从套接字接收前几个字节以确定缓冲区大小。

4
我正在使用、和编写一个分布式系统。
对于我每个消息,我需要接收前5个字节以知道传入消息的完整长度。
最好的方法是什么?
1.只接收5个字节,然后再次接收。如果我选择这个方法,是否可以安全地假设在recv中我会得到0或5个字节(即不写循环来继续尝试)?
2.使用MSG_PEEK
3.接收一些较大的缓冲区大小,然后读取前5个字节并分配最终缓冲区。

除非您处于非阻塞模式,否则不会收到零字节。 recv()的结果为零表示EOS,即流的结束,此时您通常应该关闭套接字。 - user207421
3个回答

4

你不需要了解太多。TCP是一种流协议,在任何时候你可以获取一个字节,也可以获取多兆字节的数据。正确而且唯一使用TCP套接字的方法是在循环中读取。

char buf[4096];        // or whatever

std::deque<char> data;

for (int res ; ; )
{
    res = recv(fd, buf, sizeof buf, MSG_DONTWAIT);

    if (res == -1)
    {
        if (errno == EAGAIN || errno == EWOULDBLOCK)
        {
            break;  // done reading
        }
        else
        {
            // error, break, die
        }
    }
    if (res == 0)
    {
        // socket closed, finalise, break
    }
    else
    {
        data.insert(data.end(), buf, buf + res);
    }
}

循环的唯一目的是将数据从套接字缓冲区传输到您的应用程序。然后,您的应用程序必须 独立地 决定队列中是否有足够的数据来尝试提取某种更高级的应用程序消息。
例如,在您的情况下,您应该检查队列的大小是否至少为5,然后检查前五个字节,并检查队列是否包含完整的应用程序消息。如果没有,您应该终止操作;如果是,则提取整个消息并从队列的开头弹出它。

我承认,我对C++有点陌生(来自于C语言的背景)。我打算将接收缓冲区放入std::string中,以便可以使用协议缓冲区进行反序列化。将存储在deque<char>中的数据转换为字符串是否会有很大的开销? - Murph
@Murph:deque的优点在于从两端操作都很便宜。如果你处理字符串中的部分数据,那么相对来说会比较昂贵,因为你需要移动大量的数据。但这只是一个小差别。 - Kerrek SB
但是,如果我首先接收大小(考虑到这种情况),我可以预先分配正确大小的字符串,然后缓冲区将顺序填充 - 与填充双端队列大致相同的工作量,但最终成为连续的,因此我可以将其传递给所需的反序列化方法。 - Murph
@Murph:如果你能接收到所需的数据量,那么这将起作用。但是这个习惯用语并不适用于非阻塞操作(和边缘触发轮询!),在这种情况下,你无法选择有多少数据已准备好。 - Kerrek SB

1

使用具有两个状态的状态机:

第一个状态。

将字节接收到缓冲区中。当有5个或更多字节时,对这些前5个字节进行检查,并可能处理缓冲区的其余部分。切换到第二个状态。

第二个状态。

接收并处理字节,直到消息结束。


0

具体回答你的问题:

  1. 不能假设你会得到0或5,也有可能得到1-4。像其他人建议的那样,循环直到得到5或错误。
  2. 我不会费心使用PEEK,大多数情况下你会被阻塞(假设是阻塞调用)或得到5,所以跳过额外的堆栈调用。
  3. 这也可以,但增加了复杂性而获得的收益很小。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接