如何监控Linux的UDP缓冲区可用空间?

63

我在Linux系统上有一个Java应用程序,它打开UDP socket并等待消息。

在重负载几个小时后,会出现数据包丢失的情况,即内核接收到数据包,但我的应用程序未能接收到这些数据包(我们在抓包器中看到了丢失的数据包,我们在netstat中看到了丢失的UDP数据包,但在我们的应用程序日志中却没有看到这些数据包)。

我们尝试扩大socket缓冲区,但是没有效果 - 我们开始比以前更晚地丢失数据包,但仅此而已。

为了调试,我想知道任何时刻操作系统的udp缓冲区有多满。我已经搜索过了,但没有找到任何内容,你能帮忙吗?

附言:各位,我知道UDP不可靠。但是-我的计算机接收到所有的UDP消息,而我的应用程序无法消费其中的一些消息。我想将我的应用程序优化到最大程度,这就是我提出这个问题的原因。谢谢。

4个回答

80

UDP是一个完全可行的协议。这只不过是正确工具用于正确工作的老问题!

如果您有一个程序等待UDP数据包,然后对其进行处理并返回以等待另一个数据包,则您的处理时间需要始终快于数据包到达的最坏情况。如果不是这样,那么UDP套接字接收队列将开始填充。

短暂的爆发可以容忍这种情况。队列正是它应该做的事情——在您准备好之前排队数据包。但如果平均到达速率经常导致队列积压,那就是时候重新设计程序了。这里有两个主要选择:通过巧妙的编程技巧减少经过的处理时间,和/或将程序多线程化。还可以跨多个程序实例进行负载平衡。

如上所述,在Linux上,您可以检查proc文件系统以获取有关UDP正在进行的状态信息。例如,如果我cat节点/proc/net/udp,我会得到类似以下内容:

$ cat /proc/net/udp   
  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode ref pointer drops             
  40: 00000000:0202 00000000:0000 07 00000000:00000000 00:00000000 00000000     0        0 3466 2 ffff88013abc8340 0           
  67: 00000000:231D 00000000:0000 07 00000000:0001E4C8 00:00000000 00000000  1006        0 16940862 2 ffff88013abc9040 2237    
 122: 00000000:30D4 00000000:0000 07 00000000:00000000 00:00000000 00000000  1006        0 912865 2 ffff88013abc8d00 0         

由此可见,用户ID为1006的套接字正在监听0x231D端口(8989),接收队列大约为128KB。由于128KB是我的系统最大大小,这告诉我我的程序在跟上到达的数据报方面非常薄弱。到目前为止已经有2237个数据包被丢弃了,这意味着UDP层不能将任何更多的数据报放入套接字队列中,必须丢弃它们。

您可以观察程序随时间的行为,例如使用:

watch -d 'cat /proc/net/udp|grep 00000000:231D'

还要注意,netstat命令执行的是相同的操作:netstat -c --udp -an

我的微不足道的程序的解决方案将是多线程。

干杯!


1
你如何知道你的系统最大UDP队列大小是128KB? - Chinaxing
5
这段指令的意思是查询当前系统的最大接收缓冲区大小。 - wakjah
@wakjah 这是总体还是一个端口的情况? - experiment unit 1998X

51

Linux提供文件/proc/net/udp/proc/net/udp6,分别列出所有打开的UDP套接字(IPv4和IPv6)。在这两个文件中,列tx_queuerx_queue显示以字节为单位的发送和接收队列。

如果一切正常,你通常不会在这两个列中看到与零不同的值:只要你的应用程序生成数据包,它们就会通过网络发送,只要这些数据包从网络中到达,你的应用程序就会唤醒并接收它们(recv调用会立即返回)。 如果你的应用程序已经打开了套接字但未调用recv来接收数据,或者未能快速处理此类数据,则可能会看到rx_queue上升。


1
@Juliano谁说他可以选择要使用的协议?也许他正在实现一个基于UDP的协议来为现有客户提供服务。 - steffen
2
海报想了解有关监视UDP统计信息的内容,而不是关于使用哪种协议的意见。通过首先确定丢失发生在哪些层,然后可以着手解决问题。 - RickS

5

rx_queue可以告诉你在任何时刻队列的长度,但它不能告诉你队列有多满,即高水位标记。没有办法不断监视这个值,也没有办法通过编程方式获取它(请参见如何获取UDP套接字排队数据的数量?)。

我唯一能想象的监视队列长度的方法是将队列移动到您自己的程序中。换句话说,启动两个线程--一个尽可能快地读取套接字并将数据报文转储到您的队列中;另一个是您的程序从此队列中拉取并处理数据包。当然,这假设您可以确保每个线程在单独的CPU上运行。现在,您可以监视自己队列的长度并跟踪高水位标记。


3
这个流程很简单:
  1. 如果需要,暂停应用程序进程。

  2. 打开UDP套接字。如果需要,您可以从运行中的进程使用/proc/<PID>/fd获取它。或者您可以将此代码添加到应用程序本身并向其发送信号——当然,它已经打开了套接字。

  3. 尽可能快地在紧密的循环中调用recvmsg

  4. 计算您收到的数据包/字节数。

这将丢弃当前缓冲的任何数据报,但如果这会破坏您的应用程序,则说明您的应用程序已经出现问题。


6
我本来想给这个回答点个踩,但转念一想觉得它太好笑了。我只是希望没有人尝试去实现它 :) - Navin

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