3

我有一个可能很愚蠢的问题要问,我环顾四周,但没有看到直接的答案,我想我可能会在这里得到一个快速的答案。在使用 bsd 套接字的简单 TCP/IP 客户端-服务器选择循环中,如果客户端发送两条同时到达服务器的消息,服务器上对 recv 的调用是否会返回捆绑在缓冲区中的两条消息,还是 recv 强制每个不同的到达消息要单独阅读?

我问是因为我在一个无法判断客户端如何构建要发送的消息的环境中工作。通常,recv 报告读取了 12 个字节,然后是 915,然后是 12 个字节,然后是 915,依此类推,以这种交替的 12 到 915 模式......但有时它会报告 927(即 915+12)。我在想,要么是客户端在将它的一些信息发送到服务器之前将其捆绑在一起,要么是消息在调用 recv 之前到达,然后 recv 同时提取所有待处理的字节。所以我想确保我正确理解了 recv 的行为。我想我的理解可能在这里遗漏了一些东西,我希望有人能指出它,谢谢!

4

3 回答 3

7

TCP/IP 是基于流的传输,而不是基于数据报的传输。在流中, 和 之间没有一对一的相关send()recv()。这仅适用于数据报。因此,您必须准备好处理多种可能性:

  1. 单个调用send()可能适合单个 TCP 数据包,并通过单个调用完整读取recv().

  2. 一次调用send()可能跨越多个 TCP 数据包,需要多次调用recv()才能读取所有内容。

  3. 多个调用send()可能适合单个 TCP 数据包,并通过单个调用完整读取recv().

  4. 多次调用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()以读取下一个消息长度/标题,依此类推。

于 2013-01-11T22:44:56.313 回答
1

在单个 recv 调用中返回两条消息绝对是有效的(请参阅Nagle 算法)。TCP/IP 保证顺序(消息中的字节不会混合)。除了在单个调用中一起返回它们之外,单个消息还可能需要对 recv 进行多次调用(尽管对于像描述的那样小的数据包不太可能)。

于 2013-01-11T21:06:59.070 回答
0

您唯一可以依靠的是字节的顺序。您不能指望它们如何划分为 recv 调用。有时事情会在端点或沿途合并。事情也可能在此过程中被打破,因此独立到达。听起来您的发件人正在交替发送 12 和 915,但您不能指望它。

于 2013-01-11T21:06:59.780 回答