TCP/IP 是基于流的传输,而不是基于数据报的传输。在流中, 和 之间没有一对一的相关send()
性recv()
。这仅适用于数据报。因此,您必须准备好处理多种可能性:
单个调用send()
可能适合单个 TCP 数据包,并通过单个调用完整读取recv()
.
一次调用send()
可能跨越多个 TCP 数据包,需要多次调用recv()
才能读取所有内容。
多个调用send()
可能适合单个 TCP 数据包,并通过单个调用完整读取recv()
.
多次调用send()
可能跨越多个 TCP 数据包,并且需要recv()
对每个数据包进行多次调用。
为了说明这一点,请考虑正在发送两条消息 -send("hello", 5)
和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()
以读取下一个消息长度/标题,依此类推。