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() 以读取下一个消息长度/标题,等等。