【问题标题】:Is recv(bufsize) guaranteed to receive all the data if sended data is smaller then bufsize?如果发送的数据小于 bufsize,recv(bufsize) 是否保证接收所有数据?
【发布时间】:2021-08-03 04:12:04
【问题描述】:

例如:

客户端

...
socket.connect(server_address)
data = some_message_less_than_100_bytes
socket.sendall(data)
...

服务器端

...
socket.accept()
socket.recv(1024)
...

服务器端是否保证接收到一个recv()的数据?

如果没有,使用标头指定消息长度的标准解决方案如何工作? 标头本身可能已被拆分,我们必须检查标头是否已正确接收。 还是标题是固定长度的?这样无论发送多少个数据,接收者都可以始终以相同的方式解释前几个字节?

其实我正在尝试做这样的事情

客户

while():
    send()
    recv()

服务器

recv()
while():
    send() # Acknowledge to client
    recv()

这是 ravi 在Linux socket: How to make send() wait for recv() 中建议的

但我发现了上面描述的问题。

ravi 的回答是否假设客户端和服务器都将接收对方在单个 recv() 中发送的内容?

更新

我很想发布图片,但由于声誉低,我不能发布...

以下链接是 HTTP 帧格式

https://datatracker.ietf.org/doc/html/rfc7540#section-4

它确实使用了固定长度的解决方案,因此无论标题分割多少块,它都可以以相同的方式工作。

所以我猜,某种“固定”长度是唯一的解决方案?即使标头大小本身是可变的,它也可能有一些承诺的位来指示标头的长度。我说的对吗?

【问题讨论】:

  • 这一切都取决于你没有指定的套接字type
  • 哦,我是说tcp
  • @성진영 在您的更新中,HTTP/2 帧是描述可变长度有效负载的固定长度标头。您将读取 9 字节的标头,然后解释其前 3 个字节以确定有效负载的长度,然后读取指定的字节数。这只是标头+有效负载格式的一个示例。 FIXED-length headers 当然不是 TCP 的要求(即 HTTP 1.1 和更早的版本没有使用 Frames,他们使用 VARIABLE-length CRLF-delimited headers 代替)
  • 感谢您的澄清。更新部分的最后一条语句呢?可变长度标头是否也使用一些固定位来指示标头的长度,就像固定标头如何帮助通知有效负载一样?
  • 问题已解决。定界符可用于完全避免任何类型的固定位。所以要么是“承诺的职位”,要么是“承诺的角色”。

标签: sockets networking network-programming


【解决方案1】:

服务器端是否保证在一个recv()中接收数据?

没有。 TCP 是字节流,而不是消息协议。虽然在大多数情况下它可能适用于小消息和空发送缓冲区,但如果数据发送大于底层数据链路的 MTU,它将开始失败。 TCP 不保证任何 atomar send-recv 对,但仅针对单个八位字节。所以即使是小数据也不要指望它。

【讨论】:

  • 哦,您确认我认为recv() 将接收多个字节是错误的。那挺好的。实际上,在搜索 HTTP 实现指南时,我发现 HTTP 有 72 位固定帧头,其中前 24 位表示后面的有效负载的长度。所以我猜,HTTP 连接中发生的事情正是“这样,无论发送多少个数据,接收方总能以相同的方式解释前几个字节?”
  • "我发现 HTTP 有 72 位固定帧头" - HTTP/2 有。早期版本的 HTTP 没有。
  • @JinyoungSung:固定长度消息或包含消息长度的固定长度前缀是在 TCP 提供的字节流之上广告消息语义的常用方法。 HTTP/1 虽然不同:有一个可变长度的标头,带有一个固定的分隔符字符串(空行)。消息正文的大小通常包含在此可变长度标头中(如Content-length),但也可以采用不同的方式(如使用 HTTP 分块传输编码)。
【解决方案2】:

服务器端是否保证在一个recv()中接收数据?

对于 UDP,是的。 recv() 将返回 1 个完整的数据报或错误。但是,如果缓冲区大小小于数据报,则数据将被截断,您无法恢复。

对于 TCP,没有。您唯一的保证是,如果没有发生错误,那么recv() 将返回至少 1 个字节但不超过 指定的缓冲区大小,它可以返回任意数量的之间的字节数。

如果没有,使用标头指定消息长度的标准解决方案如何工作?标头本身可能已被拆分,我们必须检查标头是否已正确接收。还是header是固定长度的?

它可以采用任何一种方式,具体取决于标题的特定格式。许多协议使用固定长度的标头,许多协议使用可变长度的标头。

无论哪种方式,您可能必须多次调用send() 以确保发送所有相关字节,并多次调用recv() 以确保您收到所有字节。 TCP 中的发送和读取之间没有 1:1 的关系。

ravi 的回答是否假设客户端和服务器都将接收对方在单个 recv() 中发送的内容?

Ravi 的回答对send() 发送和recv() 接收的字节数没有任何假设。他的回答以更高层次的视角呈现。但是,强制要求的行为非常简单,例如:

int sendAll(int sckt, void *data, int len)
{
    char *pdata = (char*) data;
    while (len > 0) {
        int res = send(sckt, pdata, len, 0);
        if (res > 0) {
            pdata += res;
            len -= res;
        }
        else if (errno != EINTR) {
            if ((errno != EWOULDBLOCK) && (errno != EAGAIN)) {
                return -1;
            }
            /*
            optional: use select() or (e)poll to
            wait for the socket to be writable ...
            */
        }
    }
    return 0;
}

int recvAll(int sckt, void *data, int len)
{
    char *pdata = (char*) data;
    while (len > 0) {
        int res = recv(sckt, pdata, len, 0);
        if (res > 0) {
            pdata += res;
            len -= res;
        }
        else if (res == 0) {
            return 0;
        }
        else if (errno != EINTR) {
            if ((errno != EWOULDBLOCK) && (errno != EAGAIN)) {
                return -1;
            }
            /*
            optional: use select() or (e)poll to
            wait for the socket to be readable ...
            */
        }
    }
    return 1;
}

这样,您可以使用sendAll() 发送消息头后跟消息数据,recvAll() 接收消息头后跟消息数据。

【讨论】:

  • 它是调用者需要的任何大小。它不仅限于标题。例如,Macattack's answer 与 Ravi 回答的问题相同,可以调整为使用 sendAll()/recvAll(),如下所示:int sendLen = strlen(sendBuff); sendAll(sockfd, &sendLen, sizeof(sendLen)); sendAll(sockfd, sendBuff, sendLen); ... int len = 0; recvAll(connfd, &len, sizeof(len)); recvBuff = malloc(len); recvAll(connfd, recvBuff, len); ... free(recvBuf);
  • @성진영 "我认为即使在可变长度标头中,也会有一些“承诺位”来指示标头的长度或有效负载的开始位置" - 可能有,也可能没有。这完全取决于协议的设计。许多常用的 Internet 协议使用可变长度的标头,根本没有任何固定位。例如 HTTP/2 之前的 HTTP 版本,它使用由 CRLF 分隔的行组成的可变长度标头,这些行由 2 个连续的 CRLF 终止。标头包含描述消息正文的存在和长度的行。
  • @성진영 "在 Macattack 的回答中,我认为他实际上使用了“int 大小”的前几个字节作为承诺的固定长度来指示有效负载的长度? i>" - 不,他使用int 的值来指定有效负载长度。 int 本身就是标题。他将int 的值设置为字符串的长度,然后将int 作为标题发送,然后将字符串的字符作为有效负载发送。接收方读取int作为报头,然后读取指定数量的字符作为有效载荷。
  • @성진영 "总而言之,在单个套接字通信中只有两种方法可以发送可变长度的消息" - 基本上,是的。您可以 1)在发送数据之前发送数据的长度(不需要固定长度的标头,但它会有所帮助)。阅读长度(无论多么需要),然后阅读它说的多少;或 2) 发送后跟唯一终止符的数据。读取直到收到分隔符。
  • @성진영 "所以通常它是 4 个字节。接收者假定前 4 个字节作为标头,根据接收者的环境,这可能不是真的" - a无论环境如何,优秀的协议设计者都会明确使用的格式。例如,通过在 big-endian(网络字节顺序)中使用 uint32_t。发送方可以在发送整数前使用htonl(),接收方收到后可以使用ntohl()
猜你喜欢
  • 2013-07-18
  • 1970-01-01
  • 2021-08-31
  • 2014-04-01
  • 2012-11-08
  • 1970-01-01
  • 2018-03-28
  • 2011-05-29
  • 1970-01-01
相关资源
最近更新 更多