recv() 无法读取最后一个块

recv() fails to read last chunk

下面的函数可以完美读取传入数据,但前提是 last 数据块比 BUFFSIZE 。如果最后一个 buff 的大小恰好等于 BUFFSIZE,则程序会在下一次循环迭代中再次尝试 recv,并将 bytes 设置为 1844674407379551615(很可能整数溢出)并在无限循环中重复这个......为什么?为什么不是0?为什么它在这个阶段没有跳出循环?

std::string getMsg (int clientFileDescriptor, int timeout_sec)
{
    std::string msg;
    msg.reserve(BUFFSIZE * 10);

    char buff[BUFFSIZE + 1];

    signal(SIGPIPE, SIG_IGN);

    //set a timeout for reading and writing
    struct timeval tv;
    tv.tv_sec = timeout_sec;
    tv.tv_usec = 0;
    setsockopt (clientFileDescriptor, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));


    while (true)
    {
        size_t bytes = recv (clientFileDescriptor, buff, BUFFSIZE, 0);

        std::cout << "bytes read: " << bytes << std::endl;

        if (0 < bytes && bytes <= BUFFSIZE)
        {
            msg.append(buff, bytes);

            //'buff' isn't full  so this was the last chunk of data 
            if (bytes < BUFFSIZE)
                break;
        }
        else if (!bytes) //EOF or socket shutdown by the client
        {
            break;
        }
        else
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) 
                continue;
            
            //We cannot continue due to other erros (e.g. EBADF/socket not available)
            break;
        }
    }

    return msg;
}

可能 size_t 在您的平台上未签名。因此,如果 recv 超时,recv returns -1 和 bytes 就会溢出。由于溢出,您的代码无法正确处理这种情况。

这是错误的:

        //'buff' isn't full  so this was the last chunk of data 

完全有可能 buff 未满,因为尚未收到最后一块数据。如果您的应用程序协议支持不同大小的消息,您实际上应该添加代码来检测消息的结尾,而不是依靠超时来为您找到它。

很遗憾,您没有给我们连接另一端的代码,也没有说明您的应用程序协议是如何工作的,所以我不知道对方做什么或期望什么。所以没法给你更多有用的建议。

If the size of the last buff happens to be equal to BUFFSIZE, then the program tries to recv again in the next loop iteration and sets bytes to 1844674407379551615 (most probably integer overflow) and repeats this in an infinite loop... Why?

因为没有更多的数据可以从套接字中读取,并且您正在使用读取超时,所以recv()失败并且returns -1(和errno 将是 EAGAINEWOULDBLOCK)。 recv() returns a signed ssize_t,而不是 unsigned size_t . size_t 不能保持负值,因此如果您将 signed -1 分配给 size_t,它将换行到最大的正值size_t 可以容纳。

Why isn't it 0?

因为不可能。 recv() 不会 return 0 超时,仅在正常断开时。

And why doesn't it escape the loop at this stage?

因为 recv() 是 returning -1。因此,if (0 < bytes && bytes <= BUFFSIZE)if (!bytes) 都是假的,所以你的逻辑落入你最后的 else 块,它正在检查 errno 然后 continue'ing 循环在 EAGAINEWOULDBLOCK 错误时(如果您只是要忽略它,设置读取超时有什么意义?)。然后 recv() 超时,return 再次 -1,你的逻辑 continue 再次循环,一遍又一遍,永远。

TCP 没有消息边界的概念,因此您必须在代码中手动处理。有几种不同的方法可以解决这个问题:

  1. 设置一个合理的超时,然后在超时结束时中断循环(并关闭连接)。这对于检测完整消息的结尾是不可取的,因为您不知道超时是因为达到了 end-of-data 还是网络 failure/hiccup 发生了。另外,如果发件人恰好在超时时间内发送了一条新消息,您会将其视为上一条消息的延续。因此,您需要一种更 明确 的方法来确定每条消息的真正完成,并根据实际错误相应地失败。

  2. 在这种情况下,如果您预先知道大小,则可以在发送实际数据之前发送数据的完整大小。然后,无论您如何读取块,只要在收到指定数量的字节后就停止读取循环。不要阅读超过你被要求阅读的内容。

  3. 否则,您可以在每个块前加上一个 header 前缀,指定块中有多少数据字节。发送完所有数据后,发送最后一个数据大小为 0 的块。当收到数据大小为 0 的块时,停止读取循环。

  4. 否则,只需优雅地在发送完所有数据后关闭套接字连接。当 recv() returns <= 0 时停止读取循环,仅当 recv() 已 returned 0 时才处理消息,并在以下情况下处理错误recv() 改为 returned -1