包含消息长度的两字节标头上的部分 recv() 怎么样?
Posted
技术标签:
【中文标题】包含消息长度的两字节标头上的部分 recv() 怎么样?【英文标题】:What about partial recv() on two byte header containing message length? 【发布时间】:2016-12-16 09:28:49 【问题描述】:我一直在阅读一些套接字指南,例如 Beej 的网络编程指南。现在很清楚,不能保证在单个 recv() 调用中接收到多少字节。因此,例如机制应发送说明消息长度的前两个字节,然后发送消息。所以接收者接收前两个字节,然后循环接收,直到接收到整个消息。一切都很好,花花公子!?
一位同事问我有关消息不同步的问题。例如。如果不知何故,我在一次 recv() 调用中收到两个字节,它们实际上位于消息本身的中间,并且它会显示为某个值的整数?这是否意味着发送的其余数据将不同步?那么部分接收报头呢,即一次一个字节?
也许这是多虑了,但我在任何地方都找不到提到这一点,我只是想确保如果它可能对通信的完整性构成威胁,我会处理这个问题。
谢谢。
【问题讨论】:
为什么消息会不同步?如果您使用 TCP 连接,则操作系统的 TCP/IP 堆栈负责正确地重新排列数据包并以正确的顺序传送字节。如果您使用 UDP,那么您将获得内部字节顺序正确的整个数据报。只有完整的数据报可能出现故障。 好的。想象一下消息由 2 个字节的标头和 10 个字节的有效负载组成。我连接并开始接收一些东西。出于某种原因,发送方开始发送有效负载而不是标头,并且由于无法保证我一次收到全部 10 个字节,因此它可能看起来像这样:假设地说,4 字节、3 字节、2 字节和最后 1 字节。在有效载荷的中间,我收到 2 个字节,因此假设这是一个标头。如果我真的不走运,这些字节可能代表一个数字,例如 21。然后我假设我期望有效负载为 21 个字节,依此类推...... 那行不通。发送方知道何时建立连接。他不能从消息中间开始,而必须始终传输完整的有效消息。否则任何接收器都无法可靠地处理数据。 【参考方案1】:这不是想太多。 TCP 呈现一个流,所以你应该这样对待它。很多关于 TCP 的问题是由于网络问题,在开发过程中可能不会发生。
以您可以查找的(4 字节)魔法开始一条消息,后跟按预期顺序(通常为大端)的(4 字节)长度。接收时,读取当时标头的每个字节,这样无论接收到的字节如何,您都可以处理它。基于此,您可以在持久的 TCP 连接中接受消息。
请注意,在每条消息开始新连接时,您知道起点。不过,如果只是过滤掉一些无效消息,发送魔法也无妨。
校验和不是必需的,因为 TCP 显示了一个可靠的字节流,它已经被 TCP 的接收部分检查过了,并且只有在发送/接收出现编码问题时才需要同步。
另一方面,UDP 发送数据包,因此您知道会发生什么,但无法保证交付和订单。
【讨论】:
【参考方案2】:你的同事弄错了。 TCP 数据不能乱序到达。但是,您应该调查recv()
的 MSG_WAITALL 标志,以克服两个长度字节分别到达的可能性,并在接收消息正文时消除循环的需要。
【讨论】:
【参考方案3】:让你的客户端和服务器同步在一起是你的责任,但是在 TCP 中没有乱序交付,如果你通过调用 recv() 得到了一些东西,你可以认为背后没有任何东西你没有'没有收到。
那么问题是如何同步发送者和接收者?正如 stefaanv 所说,这很容易,发送者和接收者都知道他们的起点。所以你可以为你的网络通信定义一个协议。例如,可以这样定义协议:
4 字节的报头,包括消息类型和负载长度
消息的其余部分是有效负载长度
因此,您必须在发送实际有效负载之前发送 4 字节标头,然后再发送实际有效负载。
由于 TCP 已保证 Inorder 可靠交付,您可以为每个包调用两次 recv()。一个长度为 4 字节的 recv() 调用用于获取下一个有效负载大小,另一个调用 recv() 其大小在标头中指定。有必要让两个 recv() 一直阻塞以保持同步。
一个例子是这样的:
#define MAX_BUF_SIZE 1024 // something you know
char buf[MAX_BUF_SIZE];
int recvLen = recv(fd, buff, 4, MSG_PEEK);
if(recvLen==4)
recvLen = recv(fd, buff, 4);
if(recvLen != 4)
// fatal error
int payloadLen = extractPayloadLenFromHeader(buf);
recvLen = recv(fd, buff, payloadLen, MSG_PEEK);
if(recvLen == payloadLen)
recvLen = recv(fd, buff, payloadLen); // actual recv
if(recvLen != payloadLen)
// fatal error
// do something with received payload
如您所见,我首先使用 MSG_PEEK 标志调用 recv 以确保是否真的有 4 个字节可用,然后收到实际的标头。有效载荷相同
【讨论】:
即使是同步的,也不能保证收到您请求的字节数。始终检查结果的长度是否正确。for(int i = 0; i < 4; ) int recvLen = recv(fd, buff, 1); if (recvLen == 1) len << 1; len += buff; ++i;
不是用于接收多个字节的夸张代码,它会使程序明显变慢。它甚至仍然缺少处理套接字错误的代码(recvLen
我错过了,通常我会用 MSG_PEEK 调用 recv 以确保是否有 N 个字节可用
没问题,这是另一种方法,但是,看看你的 else 代码会很有趣
我刚刚在第二条评论中看到了我的错字。 “它将使...”显然应该是“它不会使...”以上是关于包含消息长度的两字节标头上的部分 recv() 怎么样?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 sys socket recv 函数不填充数据但返回字节长度?