【问题标题】:Successive calls to recvfrom() loses data?连续调用 recvfrom() 会丢失数据?
【发布时间】:2010-10-15 02:50:49
【问题描述】:

我正在开发一个使用 UDP 的可靠文件传输程序。 (计算机网络课程。)

我的问题是——好吧,考虑一下这种情况:

  1. 发送者有(例如)12 字节的数据要发送。所以发送者执行这个调用:

    sendto(fd, &buf, 12, 0, (struct sockaddr *)&cliaddr,sizeof(cliaddr));
    

    这会以不可靠的方式发送 12 个字节的数据。该数据的前 4 个字节恰好是“消息长度”字段。在这种情况下,前 4 个字节的值可能为 0x0000000C

  2. 接收方想要使用 recvfrom() 读取前 4 个字节。看到段大小是 12 字节,它想读取剩余的 8 字节。所以接收器可能看起来像这样:

    /* read the segment size */
    recvfrom(sockfd,&buf,4,0,(struct sockaddr *)&cliaddr,&len);
    
    /* do some arithmetic, use bzero(), etc */
    
    /* read the rest of the data */
    recvfrom(sockfd,&buf,8,0,(struct sockaddr *)&cliaddr,&len);
    

当我执行此代码时,我可以毫无问题地接收前 4 个字节。但是当我尝试获取剩余数据时,这些数据似乎丢失了。在我的输出中,我得到了垃圾 - 它看起来像 next 12 个字节的一部分,发件人正在 sendto()-ing。

这是预期的行为吗?也就是说,如果单个recvfrom()调用没有读取到所有发送的数据,是不是不能保证数据(剩下的8个字节)可供我使用?

似乎发送段标头(包括其大小)和有效负载的标准方法不起作用。这是否意味着我需要发送 2 个单独的段 - 一个仅包含标头信息,然后是带有有效负载的第二个段?还是我只是错误地使用了这些系统调用(或者是否有我缺少的标志或 setsockopt()?)

【问题讨论】:

  • 重点是UDP是包式协议,不是管道式协议。因此,它需要一次性为您提供整个数据包,以便您知道数据包的开始和结束位置。否则你将不知道到底发生了什么。

标签: c sockets udp


【解决方案1】:

另一种方法是使用 MSG_PEEK 标志执行虚拟 recvfrom。虽然返回的大小与您的缓冲区大小相同(或更多),但请获取更大的缓冲区并重试。 然后再次执行 recvfrom(不带 MSG_PEEK 标志)以从 UDP 缓冲区中删除消息。

当然,这是相当低效的,当您可以决定最大数据包大小时不应该这样做。

【讨论】:

  • 这不起作用。假设您执行了虚拟的 recvfrom,然后在执行“真正的”recvfrom 之前,数据报被丢弃了。然后,您会将下一个数据报放入错误大小的缓冲区,可能会截断它。
【解决方案2】:

来自 recv(2) 手册页:

如果消息太长而无法放入 提供的缓冲区,多余的字节可能是 根据类型丢弃 接收消息的套接字。

这就是发生在你身上的样子。

您应该有一个最大消息大小的缓冲区并读取该数量。您将只读取一个数据报并返回长度。然后,您可以解析缓冲区前面的长度,并根据 recvfrom(2) 返回的内容对其进行验证。

【讨论】:

  • 也就是说最大segment size必须是固定的,并且客户端和服务端都提前知道?
  • UDP 数据报的最大大小为 64KiB。您可能无法发送这么多,具体取决于发送缓冲区的大小。 (MSS 是 TCP 术语,顺便说一句)。
  • 由于您正在设计此协议,请随意选择最大尺寸。 UNP Vol1,第 3 版建议使用比您预期接收的最大消息大一个字节的缓冲区。如果 recvfrom() 返回的值等于缓冲区的长度,则应将其视为错误。
猜你喜欢
  • 2013-10-21
  • 2016-05-05
  • 2021-07-31
  • 2013-03-27
  • 1970-01-01
  • 2020-05-13
  • 2014-04-18
  • 2014-12-05
  • 2018-01-27
相关资源
最近更新 更多