Linux UDP数据包丢失的原因

14
我有一个Linux C++应用程序,可以接收顺序UDP数据包。由于排序,我可以轻松确定何时会发生数据包丢失或重排序,即遇到“间隙”时。系统有一种恢复机制来处理这些间隙,但最好是在第一时间避免间隙的出现。使用基于简单libpcap的数据包嗅探器,我已经确定硬件级别上没有数据间隙。然而,在我的应用程序中看到了很多间隙。这表明内核正在丢弃数据包;通过查看/proc/net/snmp文件进行确认。当我的应用程序遇到间隙时,Udp InErrors计数器会增加。

在系统级别上,我们已经增加了最大接收缓冲区:

# sysctl net.core.rmem_max
net.core.rmem_max = 33554432

在应用程序层面,我们已经增加了接收缓冲区的大小:

int sockbufsize = 33554432
int ret = setsockopt(my_socket_fd, SOL_SOCKET, SO_RCVBUF,
        (char *)&sockbufsize,  (int)sizeof(sockbufsize));
// check return code
sockbufsize = 0;
ret = getsockopt(my_socket_fd, SOL_SOCKET, SO_RCVBUF, 
        (char*)&sockbufsize, &size);
// print sockbufsize

调用getsockopt()函数后,打印出的值始终是它设置值的两倍(例如上面的67108864),但我认为这是可以预料的。

我知道不能及时消耗数据可能会导致数据包丢失。但是,该应用程序所做的只是检查排序,然后将数据推入队列;实际处理工作在另一个线程中完成。此外,该机器是现代化的(双Xeon X5560,8 GB RAM)且负载非常轻。我们有成千上万个相同的应用程序以更高的速率接收数据,但不会出现这个问题。

除了应用程序消费过慢之外,Linux内核是否还有其他原因可能会丢弃UDP数据包?

顺带一提,这是在CentOS 4上,使用的是2.6.9-89.0.25.ELlargesmp内核。


1
检查你的电缆,位错误经常发生,特别是如果你把电缆绕在电噪声源周围。可能是驱动程序/网卡出现问题,看看是否可以关闭/打开校验和卸载。请记住,网络元素如交换机可能会丢失数据包。使用Wireshark监视您的流量并寻找可疑的事情。您可能需要对应用程序进行仪器化以验证您没有任何过度延迟(例如等待互斥锁太长时间),以确保您读取足够快。 - nos
2
pcap应用程序确认NIC正在接收所有数据包,因此这不是布线或交换机问题。至于程序本身,我正在使用数十个其他应用程序共享的完全相同的代码。这个连接是唯一有问题的连接。 - Matt
入站校验和可能不正确吗? - Alnitak
4个回答

9
如果您的线程数多于核心数量并且它们之间具有相等的线程优先级,则接收线程很可能因为没有足够时间来刷新输入缓冲区而饱受困扰。请考虑将该线程运行在比其他线程更高的优先级级别上。
类似地,虽然通常不太有效,但将用于接收的线程绑定到一个核心上,这样您就不必遭受在核心之间切换和相关缓存刷新的开销。

听起来确实像是读取线程在这里被饿死了。不过也可能是系统中的其他进程造成了饥饿。 - Mark B
只是想澄清一下:您所说的“刷新传入缓冲区”是否仅意味着跟上传入消息的速率,还是有更微妙的东西? - NPE
@aix:是的,基本上读取套接字上可用的所有内容。 - Steve-o
如果你运行 top 命令并输入 1 以启用每个 CPU 的显示,是否有任何 CPU/核心显示 0% 的空闲时间?如果是这样的话,那么接收线程饥饿很可能就是问题所在。 - Mark Rajcok

4
我曾经遇到过类似的问题。我的程序的任务是在一个线程中接收UDP数据包,并使用阻塞队列将它们写入另一个线程的数据库。
我注意到(使用vmstat 1),当系统经历大量I/O等待操作(读取)时,我的应用程序无法接收数据包,但它们确实被系统接收了。
问题在于,在发生大量I/O等待时,正在写入数据库的线程会在持有队列互斥锁的同时被I/O饥饿。这样,UDP缓冲区会被传入的数据包溢出,因为接收它们的主线程会挂起在pthread_mutex_lock()上。
我通过调整进程和数据库进程的离子性(ionice命令)来解决这个问题。将I/O调度类更改为Best Effort有所帮助。令人惊讶的是,即使使用默认的I/O niceness,我现在也无法再复现这个问题了。 我的内核版本是2.6.32-71.el6.x86_64。
我仍在开发这个应用程序,一旦我知道更多信息,我会尝试更新我的帖子。

1

int ret = setsockopt(my_socket_fd, SOL_SOCKET, SO_RCVBUF, (char *)&sockbufsize, (int)sizeof(sockbufsize));

首先,setsockopt需要(int, int, int, void *, socklen_t)参数,因此不需要进行强制类型转换。

通过一个基于libpcap的简单数据包嗅探器,我已经确定在硬件层面上没有数据间隙。然而,在我的应用程序中却看到了很多间隙。这表明内核正在丢弃数据包;

这表明您的环境不够快。捕获数据包是众所周知的处理密集型操作,当您在其中运行像iptraf-ng或tcpdump这样的程序时,您会观察到接口上的全局传输速率会下降。


他可能正在从另一台机器上进行捕获。 - Jim Clay
我在与应用程序相同的机器上运行pcap程序。然而,无论嗅探器是否运行,都会发生数据包丢失。换句话说,即使没有嗅探器运行,我的应用程序也会记录间隔,并且/proc/net/snmp Udp InErrors计数器会增加。(在使用嗅探器之前,我不知道/proc/net/snmp文件。) - Matt

1

我没有足够的声望来评论,但与@racic类似,我有一个程序,其中有一个接收线程和一个处理线程之间有一个阻塞队列。我注意到了同样的问题,即因为接收线程正在等待阻塞队列上的锁而丢失数据包。

为了解决这个问题,我在接收线程中添加了一个较小的本地缓冲区,并且只有在它没有被锁定时(使用std::mutex::try_lock)才将数据推入缓冲区。


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