我们在 Windows 中有一个通过 UDP 设置的客户端/服务器通信系统。我们面临的问题是,当吞吐量增长时,数据包会被丢弃。我们怀疑这是由于 UDP 接收缓冲区不断被轮询导致缓冲区被阻塞并丢弃任何传入的数据包。读取这个缓冲区是否有可能导致传入的数据包被丢弃?如果是这样,纠正这个问题的选项是什么?该系统是用 C 编写的。如果这太模糊,请告诉我,我可以尝试提供更多信息。谢谢!
6 回答
Windows 套接字中的默认套接字缓冲区大小为 8k 或 8192 字节。使用setsockopt Windows 函数来增加缓冲区的大小(请参阅 SO_RCVBUF 选项)。
但除此之外,如果您没有足够快地读取数据包,则增加接收缓冲区的大小只会延迟数据包再次被丢弃的时间。
通常,对于这种情况,您需要两个线程。
第一个线程仅用于服务套接字。换句话说,线程的唯一目的是从套接字读取一个数据包,将它添加到某种适当同步的共享数据结构中,发出一个数据包已被接收的信号,然后读取下一个数据包。
第二个线程用于处理接收到的数据包。它处于空闲状态,直到第一个线程表示已收到数据包。然后它从正确同步的共享数据结构中提取数据包并对其进行处理。然后它等待再次发出信号。
作为测试,尝试缩短数据包的完整处理,并在每次收到数据包时将消息写入控制台(或文件)。如果您可以在不丢弃数据包的情况下成功完成此操作,那么将您的功能分解为“接收”线程和“处理”线程将有所帮助。
是的,当它的缓冲区太满时,允许堆栈丢弃数据包 - 静默,甚至。这是 UDP 本质的一部分,是从 TCP 切换时放弃的可靠性之一。您可以通过添加重试逻辑、ACK 数据包等来重新发明 TCP(很糟糕),或者您可以切换到中间的东西,例如SCTP。
有一些方法可以增加堆栈的缓冲区大小,但这在很大程度上没有抓住重点。如果您的阅读速度不够快,无法保持可用的缓冲区空间,那么使缓冲区变大只会推迟您耗尽缓冲区空间的时间。正确的解决方案是在您自己的代码中创建更大的缓冲区,并尽快将堆栈缓冲区中的数据移动到程序的缓冲区中,在那里它可以等待任意长时间的处理。
第一步,增加接收缓冲区的大小,Windows 几乎允许所有合理大小的请求。
如果这没有帮助,您的消费代码似乎有一些相当慢的区域。我会使用线程,例如使用 pthreads 并利用生产者消费者模式将传入的数据报放入另一个线程的队列中,然后从那里消费,因此您的接收调用不会阻塞并且缓冲区不会满
第三步,修改您的应用程序级协议,允许在发送方批量发送数据包和批量数据包,以减少发送大量小数据包的 UDP 标头开销。
第 4 步检查您的网络设备、交换机等,可以为您提供有关其流量统计、缓冲区溢出等的详细输出 - 如果有问题,请使用更快的交换机或可能切换出故障的交换机
...仅供参考,我在我们的后端以平均水平连续运行 UDP 多播流量。~30Mbit/s,峰值为 70Mbit/s,我的丢包率几乎为零
读取这个缓冲区是否有可能导致传入的数据包被丢弃?
如果数据包到达的速度比您阅读它们的速度快,则它们可能会被丢弃。
如果是这样,纠正这个问题的选项是什么?
一种选择是更改网络协议:使用 TCP,或使用 UDP 实现一些确认 + '流控制'。
否则,您需要了解为什么您的阅读速度/频率不够快。
如果 CPU 被 100% 使用,那么您需要减少每个数据包的工作量或获得更快的 CPU(或者如果您还没有使用多线程和更多 CPU)。
如果 CPU 不是 100%,那么可能发生的情况是:
- 你读了一个数据包
- 你做了一些工作,这需要 x 毫秒的实时时间,其中一些被阻塞在其他一些 I/O 上(所以 CPU 不忙,但它没有被用来读取另一个数据包)
- 在这 x 毫秒期间,大量数据包到达,一些被丢弃
解决此问题的方法是更改线程。
另一种可能性是从套接字同时进行多次读取(您的每次读取都提供了一个可以接收 UDP 数据包的缓冲区)。
另一种可能性是查看是否有一个(O/S 特定的)配置选项来增加网络堆栈愿意缓冲的接收到的 UDP 数据包的数量,直到您尝试读取它们。
对此不确定,但在 Windows 上,不可能轮询套接字并导致数据包丢失。Windows 将数据包与您的轮询分开收集,它不会导致任何丢包。
我假设您使用 select() 来轮询套接字?据我所知,不能导致下降。
数据包可能会由于路由沿线任何地方的不相关网络流量增加或接收缓冲区已满而丢失。为了缓解这种情况,您可以增加 Winsock 中的接收缓冲区大小。
从本质上讲,UDP 是一种不可靠的协议,因为它不能保证数据包的传递,并且在传递失败时不会向发送者返回任何错误。如果您担心数据包丢失,最好将确认数据包实现到您的通信协议中,或者将其移植到更可靠的协议,如 TCP。确实没有任何其他真正可靠的方法可以防止 UDP 数据包丢失。