TCP/IP 是基于流的传输,而不是基于数据报的传输。在流中,之间不存在一对一的相关性send()
and recv()
。这仅适用于数据报。因此,您必须准备好处理多种可能性:
一次调用send()
可以放入单个 TCP 数据包中,并通过一次调用即可完整读取recv()
.
一次调用send()
可能跨越多个 TCP 数据包并需要多次调用recv()
阅读一切。
多次调用send()
可以放入单个 TCP 数据包中,并通过一次调用即可完整读取recv()
.
多次调用send()
可能跨越多个 TCP 数据包并需要多次调用recv()
对于每个数据包。
为了说明这一点,考虑正在发送两条消息 -send("hello", 5)
and send("world", 5)
。以下是调用时可能的几种组合recv()
:
"hello" "world"
"hel" "lo" "world"
"helloworld"
"hel" "lo" "worl" "d"
"he" "llow" "or" "ld"
明白了吗?这就是 TCP/IP 的工作原理。每个 TCP/IP 实现都必须考虑到这种碎片。
为了正确接收数据,逻辑消息之间必须有明确的分离,而不是单独的调用send()
,因为可能需要多次调用send()
发送一条消息,也可以发送多条消息recv()
调用以接收完整的单个消息。因此,考虑到前面的示例,让我们在消息之间添加一个分隔符:
send("hello\n", 6);
send("world", 5);
send("\n", 1);
在接收端,您可以调用recv()
需要多次,直到\n
收到字符后,您将处理收到的导致该字符的所有内容。如果完成后还有剩余的读取数据,请保存以供以后处理并开始调用recv()
再次直到下一个\n
性格等等。
有时,不可能在消息之间放置唯一的字符(也许消息正文允许使用所有字符,因此没有可用作分隔符的不同字符)。在这种情况下,您需要在消息前面加上消息的长度前缀,可以是前面的整数、结构化标头等。然后您只需调用recv()
根据需要多次,直到收到完整的整数/标头,然后调用recv()
根据需要多次读取长度/标头指定的字节数。完成后,根据需要保存所有剩余数据,然后开始调用recv()
再次读取下一个消息长度/标头,依此类推。