【问题标题】:Case when blocking recv() returns less than requested bytes阻塞 recv() 返回小于请求字节的情况
【发布时间】: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 平台感兴趣。我没有开发环境自己去看看。

【问题讨论】:

    标签: c linux sockets 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() 并且数据可用(少于请求),它将返回任何可用的数据。对吗?
    猜你喜欢
    • 2011-11-18
    • 2012-05-05
    • 1970-01-01
    • 1970-01-01
    • 2020-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多