什么可能导致非阻塞套接字阻塞“recv”?

Posted

技术标签:

【中文标题】什么可能导致非阻塞套接字阻塞“recv”?【英文标题】:What could cause a non-blocking socket to block on `recv`? 【发布时间】:2020-02-21 00:52:37 【问题描述】:

我有一个 TCP/IP 套接字设置为非阻塞,无论如何都会阻塞。套接字仅在一个线程中引用。此代码适用于 Windows(有一些调用替换),但不适用于 Linux。我有看起来像这样的代码(不要介意 C 风格的演员表——这是很久以前写的。另外,我把它修整了一点,所以如果我不小心剪掉了一步,请告诉我。很可能我'我实际上正在做那一步。实际代码在另一台计算机上,所以我无法复制粘贴。):

// In the real code, these are class members. I'm not bonkers
int mSocket;
sockaddr_in mAddress;

void CreateSocket(
    unsigned int ipAddress,
    unsigned short port)
        
    // Omitting my error checking in this question for brevity because everything comes back valid
    mSocket = socket(AF_INET, SOCK_STREAM, 0);  // Not -1

    int oldFlags = fctnl(mSocket, F_GETFL, 0);  // Not -1
    fcntl(mSocket, F_SETFL, oldFlags | O_NONBLOCK);  // Not -1

    mAddress.sin_family = AF_INET;
    mAddress.sin_addr.s_addr = ipAddress;  // address is valid
    mAddress.sin_port = htons((u_short)port);  // port is not 0 and allowed on firewall
    memset(mAddress.sin_zero, 0, sizeof(mAddress.sin_zero));

    // <Connect attempt loop starts here>
    connect(mSocket, (sockaddr*)&mAddress, sizeof(mAddress));  // Not -1 to exit loop
    // <Connect attempt loop ends here>
    // Connection is now successful ('connect' returned a value other than -1)


// ... Stuff happens ...

// ... Then this is called because 'select' call shows read data available ...
void AttemptReceive(
    MyReturnBufferTypeThatsNotImportant &returnedBytes)

    // Read socket
    const size_t bufferSize = 4096;
    char buffer[bufferSize];
    int result = 0;

    do 
        // Debugging code: sanity checks
        int socketFlags = fcntl(mSocket, F_GETFL, 0);  // Not -1
        printf("result=%d\n", result);
        printf("O_NONBLOCK? %d\n", socketFlags & O_NONBLOCK);  // Always prints "O_NONBLOCK? 2048"

        result = recv(mSocket, buffer, bufferSize, 0);  // NEVER -1 or 0 after hundreds to thousands of calls, then suddenly blocks

        // ... Save off and package read data into user format for output to caller ...
     while (result == bufferSize);

我相信,因为 AttemptReceive 被调用以响应选择,所以套接字恰好包含恰好等于缓冲区大小 (4096) 倍数的字节数。我已经用 printf 语句证实了这一点,所以它不会在第一次循环时阻塞。每次发生此错误时,在线程块之前打印的最后两行是:

result=4096
O_NONBLOCK? 2048

recv 行更改为recv(mSocket, buffer, bufferSize, MSG_DONTWAIT); 实际上“修复”了这个问题(突然间,recv 偶尔会返回 -1 和 errno EWOULDBLOCK/EAGAIN(在我的操作系统上两者都相等)),但恐怕我可以这么说,我只是在涌出的伤口上贴创可贴。有什么想法吗?

附:地址是“localhost”,但我认为这并不重要。

注意:我使用的是旧编译器(不是选择),2010 年的 g++ 4.4.7-23。这可能与问题有关。

【问题讨论】:

请检查错误。将套接字绑定到connect() 的目标地址是无效的,如果连接成功,则不可能工作,反之亦然。*您根本不需要绑定此套接字。您的循环应该在 result &gt; 0 时执行。不能保证它会是 4096 或缓冲区大小,也没有理由在它不是时停止。 请将strace 附加到该进程,并证明该进程进入recv() 调用并在其中阻塞(strace 将显示它),而不是,只是可能,点击隐藏代码块中某处的错误被谦虚地描述为“......保存并将读取数据打包为用户格式以输出给调用者......”,然后在那里无限循环。 @SamVarshavchik 我当然想到了。 gdb bt 显示线程卡在recv 我找到了这个问题,但是我看不懂 Perl,所以我不确定它是否适用:***.com/questions/11895632/… @KeithM:您提到的 perl 问题是由于套接字实际上不是非阻塞的,尽管以这种方式声明。这是由于模块中的一个错误,它没有执行 Win32 特定的非阻塞处理。换句话说:可能与您的情况无关。 【参考方案1】:

socket() 使用我的操作系统和编译器自动在套接字上设置O_RDWR,但似乎O_RDWR 在程序开始时意外地取消了相关套接字的设置(它以某种方式允许它读取如果有数据要读取则很好,否则阻塞)。修复该错误导致套接字停止阻塞。显然,至少在我的操作系统和编译器上,O_RDWRO_NONBLOCK 都需要避免套接字阻塞。

【讨论】:

fcntl(fd, F_SETFL, O_RDWR) 被忽略并且在 linux 中没有影响——在打开文件描述符后,您不能更改读/写标志。所以这不是你的问题的原因——你所做的只是让潜在的问题消失了。 @ChrisDodd 不过,我没有更改任何其他内容,并且我确认我现在偶尔会收到 EAGAIN 错误(可能之前会被阻止)。唯一的另一种可能性是 gdb 在 socketFlags 的值上对我撒谎,但我已经想到了这一点,并仔细检查了我的调试编译器设置以确保编译/链接标志是正确的。也许您的主张仅在过去十年左右的某个时间点实施。我们不能使用最新的编译器版本。我的 gcc/g++ 是 2010 年的。 事后看来,我将开始将这个事实放入我的问题中......我现在将对其进行编辑。

以上是关于什么可能导致非阻塞套接字阻塞“recv”?的主要内容,如果未能解决你的问题,请参考以下文章

如果在超时发生之前没有收到数据,Python 的 socket.recv() 会为非阻塞套接字返回啥?

非阻塞式I/O

TCP之非阻塞connect和accept

非阻塞套接字与IO多路复用

如何中止winsock阻塞调用?

检查非阻塞发送是不是成功