Java 丢失了一半的 UDP 数据包

6
我有一个简单的客户端/服务器设置。服务器是用C语言编写的,查询服务器的客户端是Java。
我的问题是,在连接中发送带宽密集型数据(如视频帧)时,会丢失一半的数据包。我确保在服务器端正确地对UDP数据包进行了分段(UDP的最大有效载荷长度为2^16)。我验证了服务器正在发送数据包(printf sendto()的结果)。但是Java似乎没有接收到一半的数据。
此外,当我切换到TCP时,所有视频帧都可以通过,但延迟开始增加,在几秒钟的运行时间后添加了数秒的延迟。
我是否遗漏了任何显而易见的事情?我无法解决这个问题。

3
你确定是“Java”出了问题而不是你的网络出了问题吗?此外,与TCP不同,UDP无法保证数据包的传递、顺序或重复。 - Nate
3
使用TCP会导致延迟这一事实告诉我,您试图将比网络承载能力更多的数据注入其中。由于您从服务器端有发送数据包的日志,因此应该可以大致了解每秒钟发送多少数据。这是否与您的网络容量兼容? - user3458
5个回答

9

获取一个像Wireshark这样的网络工具,这样您就可以看到线路上发生了什么。

UDP不会尝试重新传输数据包,因此如果某个数据包在某处丢失,程序必须处理该丢失。TCP将努力按顺序向程序传递所有数据包,丢弃重复的数据包并自行请求丢失的数据包。如果您看到高延迟,我敢打赌您也会看到TCP丢包,这将显示为服务器的重传。如果您没有看到TCP重传,则可能是客户端无法快速处理数据以跟上进度。


我打开了Wireshark并使用TCP转储了数据包。我没有看到任何丢失的数据包。ACK的往返时间(RTT)约为40毫秒。我的Java客户端使用专用线程来解析字节。 - Andrew Klofas
感谢您的帮助,问题已经解决。这是Java接收线程中的问题(该线程短时间内被阻塞,未监听数据包)。您提出的TCP丢包监视器建议很好。 - Andrew Klofas

3
任何基于UDP的应用协议都不可避免地会受到数据包丢失、重排序和(在某些情况下)重复的影响。“UDP”中的“U”可以表示“不可靠”数据报协议。通常情况下,UDP数据包的丢失是因为你的流量超出了服务器和客户端之间一个或多个“跳”的缓冲能力。当发生这种情况时,数据包就会被丢弃。由于你使用的是UDP,所以没有传输协议级别的通知来告诉你正在发生这种情况。
如果你在应用程序中使用UDP,则该应用程序需要考虑UDP的不可靠性,并实现自己的机制来处理丢失和乱序的数据包以及进行自己的流量控制。(如果一个应用程序毫无顾忌地发送UDP数据包,而不考虑其对已经过载的网络可能产生的影响,那么它就是一个糟糕的网络公民。)
在TCP的情况下,数据包可能也会被丢弃,但是TCP会检测并重新发送丢失的数据包,并且TCP流量控制机制会开始减缓数据传输速率。结果就是“延迟”。
根据OP的评论,他问题的原因是客户端在一段时间内没有“监听”,导致数据包被客户端的操作系统丢弃。解决这个问题的方法是:
1.使用一个专用的Java线程,只读取数据包并将其排队等待处理;
2.增加套接字的内核数据包队列大小。
但即使采取这些措施,仍然可能会出现数据包丢失。例如,如果机器超载,应用程序可能无法在内核必须将它们丢弃之前足够频繁地获得执行时间片来读取和排队所有的数据包。
关于UDP是否容易重复存在一些争议。的确,UDP没有天生的重复检测或预防机制。但也确实存在这样的事实:构成Internet的IP数据包路由基础设施不太可能自发地复制数据包。因此,如果确实出现了重复数据包,则很可能是发送方决定重新发送UDP数据包导致的。因此,在我看来,尽管UDP容易出现重复问题,但它本身并不会导致重复问题......除非OS协议栈或IP基础设施中存在漏洞。

如果两个数据包最终到达客户端的路径不同,那么从客户端的角度看,这些数据包不就可能被重新排序了吗? - Eric J.
为什么不允许重复?据我所知,重复是可以存在的。 - President James K. Polk
只有在发送者发送两个内容完全相同的数据包(例如,重新发送)时才会出现重复。即使接收者无法检测到这一点,在这种情况下,数据包在概念上也是不同的。据我所知,路由器不会重新发送给定的UDP数据包。 - Stephen C
@Fred:这正是我所想的 :-) - Eric J.
@EJP - “当存在多条路径时,难道不可能通过不同的路径到达相同的数据包吗?”我并不认同这种逻辑。仅仅存在多条路径并不意味着单个数据包会被发送到其中的多条路径之一。 - Stephen C
显示剩余5条评论

2
尽管UDP支持最长65535字节的数据包(包括8字节的UDP头 - 但请参见注1),但您和目标之间的底层传输不支持这么长的IP数据包。例如,以太网帧的最大大小为1500字节 - 考虑到IP和UDP头的开销,这意味着任何数据有效负载长度超过约1450的UDP数据包都可能被分成多个IP数据报。
一个最大大小的UDP数据包将被分成至少45个单独的IP数据报 - 如果其中任何一个片段丢失,整个UDP数据包就会丢失。如果底层数据包丢失率为1%,则您的应用程序将看到约36%的丢包率!
如果您想看到更少的数据包丢失,请不要发送巨大的数据包 - 将每个数据包中的数据限制在约1400字节左右(甚至可以执行自己的“路径MTU发现”以确定可以安全发送而不需要分段的最大大小)。
  1. 当然,UDP也受到IP的限制,IP数据报的最大大小为65535,包括IP头。IP头的大小范围从20到60字节,因此可在UDP数据包内传输的应用程序数据的最大量可能低至65467。

那里有几处不准确,看看我的答案。 - user207421
好的实用建议 - 即使有一些关于UDP头大小(从记忆中为8个字节)的争议。对于大多数流媒体应用程序,保持UDP数据报大小足够小以适合以太网帧具有许多优点:在帧丢失时最小化数据丢失;减少延迟,因为不需要等待整个数据报;并且 - 对于某些应用程序 - 可以通过关闭流的UDP校验和并依靠以太网错误检测来丢弃有误的帧/数据报来减少处理负载。 MPEG通常作为每个UDP数据报的7个188字节TS帧发送。 - Dipstick

0
问题可能与您的UDPSocket发送缓冲区被填满有关。仅发送由UDPSocket.getSendBufferSize()指示的一次发送的字节数量。使用setSendBufferSize(int size)来增加此值。

如果使用#send()发送大于SO_SNDBUF设置的DatagramPacket,则实现特定于是否发送或丢弃数据包。


0

IP 支持最大长度为 65535 字节的 数据包,其中包括一个 20 字节的 IP 数据包头。 UDP 支持最大长度为 65507 字节的 数据报文,加上 20 字节的 IP 头和 8 字节的 UDP 头。然而,网络 MTU 是实际限制,不要忘记这不仅包括这 28 字节,还包括以太网帧头。未分段的 UDP 的实际可用极限是最小 MTU 576 字节减去所有开销。


2
以太网使用帧而不是数据包。以太网MTU为1500字节的有效载荷,这定义了IP片段的最大大小;它不包括帧头和尾部。576是节点必须支持的最小MTU,以便传输IP片段,这不是未分段UDP的实际限制。此限制必须通过路径MTU发现来确定。此外,可以发送比MTU更大的数据报,而不会使其变得不切实际,直到数据报或数据包丢失过大。 - Juliano

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接