阻塞 recv() 返回小于请求字节的情况

Posted

技术标签:

【中文标题】阻塞 recv() 返回小于请求字节的情况【英文标题】:Case when blocking recv() returns less than requested bytes 【发布时间】:2011-01-18 17:44:27 【问题描述】:

recv() 库函数手册页提到:

它返回接收到的字节数。它通常返回任何可用的数据,直到请求的数量,而不是等待收到请求的全部数量。

如果我们使用阻塞 recv() 调用并请求 100 字节:

recv(sockDesc, buffer, size, 0); /* Where size is 100. */

如果服务器只发送 50 个字节,那么这个 recv() 会被阻塞,直到 100 个字节可用,否则它将返回接收 50 个字节。

情况可能是这样的:

仅发送 50 个字节后服务器崩溃

糟糕的协议设计,服务器只发送 50 个字节,而客户端期待 100 个字节,服务器也在等待客户端的回复(即,recv 将返回的服务器尚未启动套接字关闭连接)

我对 Linux / Solaris 平台感兴趣。我没有开发环境自己去看看。

【问题讨论】:

【参考方案1】:

当内部缓冲区中有数据要返回时,recv 将返回。如果你请求 100 个字节,它不会等到有 100 个字节。

如果您要发送 100 字节的“消息”,请记住 TCP 不提供消息,它只是一个流。如果您正在处理应用程序消息,则需要在应用程序层处理,因为 TCP 不会这样做。

在很多很多情况下,调用 recv(..., 100); 时,只有一个 recv 调用,可能无法在另一端完全读取 100 字节的 send() 调用;这里只是几个的例子:

发送 TCP 堆栈决定将 15 个写入调用捆绑在一起,而 MTU 恰好是 1460,这 - 根据到达数据的时间可能会导致客户端前 14 个调用获取 100 个字节和 15 个。调用以获取 60 个字节 - 最后 40 个字节将在您下次调用 recv() 时出现。 (但如果你用 100 的缓冲区调用 recv ,你可能会得到前一个应用程序“消息”的最后 40 个字节和下一个消息的前 60 个字节)

发送者缓冲区已满,可能是阅读器速度较慢,或者网络拥塞。在某些时候,数据可能会通过,在清空缓冲区时,最后一块数据不是 100 的倍数。

接收器缓冲区已满,而您的应用程序 recv() 该数据,它提取的最后一个块只是部分,因为该消息的整个 100 字节不适合缓冲区。

其中许多场景都很难测试,尤其是在您可能没有很多拥塞或数据包丢失的局域网上 - 随着您提高和降低消息发送/生成的速度,情况可能会有所不同。

无论如何。如果您想从套接字读取 100 个字节,请使用类似

int
readn(int f, void *av, int n)

    char *a;
    int m, t;

    a = av;
    t = 0;
    while(t < n)
        m = read(f, a+t, n-t);
        if(m <= 0)
            if(t == 0)
                return m;
            break;
        
        t += m;
    
    return t;

...

if(readn(mysocket,buffer,BUFFER_SZ) != BUFFER_SZ) 
  //something really bad is going on.


【讨论】:

感谢您的详细解释。只是确认 blocking recv() 调用是否也是如此? (即 recv() 返回的字节数少于请求的字节数) 是的,这适用于阻塞式 recv 调用。【参考方案2】:

行为由两件事决定。 recv 低水位标记以及您是否通过MSG_WAITALL 标志。如果您传递此标志,则调用将阻塞,直到收到请求的字节数,即使服务器崩溃。否则,只要套接字的接收缓冲区中至少有 SO_RCVLOWAT 字节可用,它就会返回。

SO_RCVLOWAT

将最小字节数设置为 套接字输入操作的过程。 SO_RCVLOWAT 的默认值为 1.如果SO_RCVLOWAT设置为较大的值,正常阻塞接收调用 等到他们收到 较小的低水位标记值或 要求的金额。 (他们可能会返回 低于低水位线,如果 发生错误,信号被捕获,或 接收中的下一个数据类型 队列与返回的不同, 例如带外数据)。这个选项 取一个 int 值。请注意,并非所有 实现允许此选项 设置。

【讨论】:

SO_RCVLOWAT 是套接字选项。如果我们没有设置任何套接字选项并且没有将标志传递给recv(),并且发出阻塞recv()调用,它会等待所有请求的字节吗? (socket关闭操作也没有启动) 在您在帖子中描述的情况下,它将返回 50 个字节。除非您将 MSG_WAITALL 标志传递给 recv,否则它将不会等待完整的 100 个字节。【参考方案3】:

如果您仔细阅读报价,最常见的情况是:

套接字正在接收数据。这 100 个字节需要一些时间。 进行了recv() 调用。 如果缓冲区中的字节数超过 0 个,recv() 将返回可用的内容并且不等待。 虽然有 0 个字节可用,但它会阻塞,线程系统的粒度决定了它的长度。

【讨论】:

谢谢,这意味着如果发出阻塞 recv() 并且数据可用(少于请求),它将返回任何可用的数据。对吗?

以上是关于阻塞 recv() 返回小于请求字节的情况的主要内容,如果未能解决你的问题,请参考以下文章

linux网络编程中阻塞和非阻塞socket的区别

linux socket阻塞recv怎么返回

linux网络编程中阻塞和非阻塞socket的区别

非阻塞 UDP 写入返回的字节数是不是少于请求的字节数?

C 套接字可以在没有客户端关闭连接的情况下接收 0 个字节吗?

socket recv阻塞与非阻塞error总结