什么会导致 UDP 数据包在发送到 localhost 时被丢弃?

Posted

技术标签:

【中文标题】什么会导致 UDP 数据包在发送到 localhost 时被丢弃?【英文标题】:What would cause UDP packets to be dropped when being sent to localhost? 【发布时间】:2011-12-19 14:14:54 【问题描述】:

我正在发送非常大(64000 字节)的数据报。我意识到 MTU 远小于 64000 字节(根据我的阅读,典型值约为 1500 字节),但我怀疑会发生两件事之一 - 要么没有数据报可以通过(所有大于 1500 字节会被静默丢弃或引发错误/异常),或者 64000 字节的数据报会被分成大约 43 1500 字节的消息并透明传输。

从长远来看(2000+ 64000 字节的数据报),大约 1% 的数据报被丢弃(即使是 LAN,这似乎也异常高)。我可能期望通过网络实现这一点,其中数据报可能会乱序到达、被丢弃、被过滤等等。但是,我没想到在 localhost 上运行时会出现这种情况。

是什么导致无法在本地发送/接收数据?我意识到 UDP 是不可靠的,但我没想到它在 localhost 上如此不可靠。我想知道这是否只是时间问题,因为发送和接收组件都在同一台机器上。

为了完整起见,我已经包含了发送/接收数据报的代码。

发送:

DatagramSocket socket = new DatagramSocket(senderPort);

int valueToSend = 0;

while (valueToSend < valuesToSend || valuesToSend == -1) 
    byte[] intBytes = intToBytes(valueToSend);

    byte[] buffer = new byte[bufferSize - 4];

     //this makes sure that the data is put into an array of the size we want to send
    byte[] bytesToSend = concatAll(intBytes, buffer);

    System.out.println("Sending " + valueToSend + " as " + bytesToSend.length + " bytes");

    DatagramPacket packet = new DatagramPacket(bytesToSend,
                        bufferSize, receiverAddress, receiverPort);

    socket.send(packet);

    Thread.sleep(delay);

    valueToSend++;

接收:

DatagramSocket socket = new DatagramSocket(receiverPort);

while (true) 
    DatagramPacket packet = new DatagramPacket(
            new byte[bufferSize], bufferSize);

    System.out.println("Waiting for datagram...");
    socket.receive(packet);

    int receivedValue = bytesToInt(packet.getData(), 0);

    System.out.println("Received: " + receivedValue
            + ". Expected: " + expectedValue);

    if (receivedValue == expectedValue) 
        receivedDatagrams++;
        totalDatagrams++;
    
    else 
        droppedDatagrams++;
        totalDatagrams++;
    

    expectedValue = receivedValue + 1;
    System.out.println("Expected Datagrams: " + totalDatagrams);
    System.out.println("Received Datagrams: " + receivedDatagrams);
    System.out.println("Dropped Datagrams: " + droppedDatagrams);
    System.out.println("Received: "
            + ((double) receivedDatagrams / totalDatagrams));
    System.out.println("Dropped: "
            + ((double) droppedDatagrams / totalDatagrams));
    System.out.println();

【问题讨论】:

您能否达到内部操作系统缓冲区限制?在丢弃数据包之前,操作系统只会保留这么多数据。 我遇到了同样的情况并想到了同样的事情,我真的不应该在 localhost 上丢失数据包。不幸的是,它确实发生了。您可以通过创建一个简单的 UDP 广播器来轻松创建这种情况,该广播器可以将 512 字节的消息快速广播一千次。创建一个简单的 UDP 客户端,它将接收消息并检查计数...您肯定会丢失消息。 @Randy 在我最近的实验中,这似乎只是因为缓冲区大小。增加缓冲区大小完全消除了这个问题,至少在我正在运行的条件下。 @ThomasOwens 感谢您的回复。我进行了一些测试,令我惊讶的是,通过将缓冲区大小增加到 4 兆字节或(1024 * 4096 字节),我能够在本地主机上发送超过 100,000 条 512 字节的消息而不会丢失一个数据包!这是 UDP 消息接收器的 sn-p: UdpClient listener = (UdpClient)ar.AsyncState; listener.Client.ReceiveBufferSize = 1024 * 4096; Byte[] receiveBytes = listener.EndReceive(ar, ref ipEndpoint); string receiveString = Encoding.ASCII.GetString(receiveBytes); 【参考方案1】:

概述

是什么导致无法在本地发送/接收数据?

主要是缓冲空间。想象一下,发送恒定的 10MB/秒,而只能消耗 5MB/秒。操作系统和网络堆栈跟不上,因此数据包被丢弃。 (这与 TCP 不同,TCP 提供流量控制和重传来处理这种情况。)

即使在没有溢出缓冲区的情况下消费数据,也可能存在无法消费数据的小时间片,因此系统会丢弃数据包。 (例如在垃圾收集期间,或者当操作系统任务暂时切换到更高优先级的进程时,等等。)

这适用于网络堆栈中的所有设备。当队列已满时,非本地网络、以太网交换机、路由器、集线器和其他硬件也会丢弃数据包。通过 100MB/s 以太网交换机发送 10MB/s 流,而其他人试图通过同一物理线路填充 100MB/s 将导致丢包。

增加socket buffers size 和操作系统的套接字缓冲区大小。

Linux

默认套接字缓冲区大小通常为 128k 或更小,这使得 非常几乎没有空间暂停数据处理。

sysctl

使用sysctl 增加发送(写入内存 [wmem])和接收(读取内存 [rmem])缓冲区:

net.core.wmem_max net.core.wmem_default net.core.rmem_max net.core.rmem_default

例如,将值增加到 8 兆字节:

sysctl -w net.core.rmem_max=8388608

要使设置保持不变,请同时更新/etc/sysctl.conf,例如:

net.core.rmem_max=8388608

关于调整网络堆栈的in-depth article 深入探讨了更多细节,涉及在 Linux 中如何从内核的网络驱动程序通过环形缓冲区一直到 C 的recv 调用接收和处理数据包的多个级别。本文介绍了在诊断网络问题时要监控的其他设置和文件。 (见下文。)

在进行以下任何调整之前,请务必了解它们如何影响网络堆栈。 确实有可能使您的网络无法使用。选择适合您的系统、网络配置和预期流量负载的数字:

net.core.rmem_max=8388608 net.core.rmem_default=8388608 net.core.wmem_max=8388608 net.core.wmem_default=8388608 net.ipv4.udp_mem='262144 327680 434274' net.ipv4.udp_rmem_min=16384 net.ipv4.udp_wmem_min=16384 net.core.netdev_budget=600 net.ipv4.ip_early_demux=0 net.core.netdev_max_backlog=3000

ethtool

此外,ethtool 可用于查询或更改网络设置。例如,如果$DEVICEeth0(使用ip addressipconfig 来确定您的网络设备名称),则可以使用以下方法增加RX 和TX 缓冲区:

ethtool -G $DEVICE rx 4096 ethtool -G $DEVICE 发送 4096

iptables

默认情况下,iptables 将记录有关数据包的信息,这会消耗 CPU 时间,尽管很少。例如,您可以使用以下命令禁用端口 6004 上的 UDP 数据包日志记录:

iptables -t raw -I PREROUTING 1 -p udp --dport 6004 -j NOTRACK
iptables -I INPUT 1 -p udp --dport 6004 -j ACCEPT

您的特定端口和协议会有所不同。

监控

多个文件包含有关网络数据包在发送和接收的各个阶段发生的情况的信息。在下面的列表中,$IRQ 是中断请求号,$DEVICE 是网络设备:

/proc/cpuinfo - 显示可用的 CPU 数量(有助于 IRQ 平衡) /proc/irq/$IRQ/smp-affinity - 显示 IRQ 亲和性 /proc/net/dev - 包含一般数据包统计信息 /sys/class/net/$DEVICE/queues/QUEUE/rps_cpus - 与接收数据包引导 (RPS) 相关 /proc/softirqs - 用于 ntuple 过滤 /proc/net/softnet_stat - 用于数据包统计,例如丢包、时间压缩、CPU 冲突等。 /proc/sys/net/core/flow_limit_cpu_bitmap - 显示数据包流(可以帮助诊断大小流之间的丢包) /proc/net/snmp /proc/net/udp

总结

缓冲区空间最有可能是丢包的罪魁祸首。网络堆栈中散布着许多缓冲区,每个缓冲区对发送和接收数据包都有自己的影响。网络驱动程序、操作系统、内核设置和其他因素会影响数据包丢失。没有灵丹妙药。

进一步阅读

https://github.com/leandromoreira/linux-network-performance-parameters http://man7.org/linux/man-pages/man7/udp.7.html http://www.ethernetresearch.com/geekzone/linux-networking-commands-to-debug-ipudptcp-packet-loss/

【讨论】:

我相信 Windows 不会对套接字缓冲区大小施加限制,所以我会尝试将其设置为 2-5MB。 (注意,在以太网上发送大数据报确实不是最佳选择,如果只有一个片段丢失,你会丢失整个数据报。) 是的 - 我知道丢失一个片段 = 丢失数据报。我提出了这种担忧,但它被认为对于这个特定的应用程序是可以接受的。如果我们丢失了一个数据报,我们可以处理它。我的搜索结果与 Windows 相同。这似乎可以缓解(或至少减少)问题。我什至能够消除数据包传输之间的延迟,并且 UDP 数据报的丢失可以忽略不计(由于缓冲区大小,我在丢失 1 个数据报之前发送了近 30000 个数据报)。【参考方案2】:

UDP pkts 调度可以由操作系统级别的多个线程处理。这可以解释为什么即使在 127.0.0.1 上您也无法正常收到它们。

【讨论】:

【参考方案3】:

您的期望,正如您在问题中所表达的以及在许多其他答案中所表达的那样,是错误的。即使没有路由器和电缆,以下所有情况也可能发生。

    如果您向任何接收者发送一个数据包,并且他的套接字接收缓冲区中没有空间,它将被丢弃。

    如果您发送的 UDP 数据报大于路径 MTU,它将被分割成更小的数据包,这些数据包受 (1) 的约束。

    如果一个数据报的所有数据包都没有到达,则该数据报永远不会被传递。

    TCP/IP 堆栈没有义务按顺序传送数据包或 UDP 数据报。

【讨论】:

我知道 2-4 是正确的,尽管我的问题是由 1 引起的。我只是碰巧遇到了默认缓冲区大小导致问题的情况,设法生成了一个小的代码示例演示了它,并得到了解决方案。【参考方案4】:

UDP 数据包不能保证到达目的地,而 TCP 是!

【讨论】:

如果我要发送到 127.0.0.1,我不会通过任何类型的网络。在这种情况下,我希望零损失甚至没有损失。 TCP 和 UDP 是两种不同的发送和接收数据包的方式。网络不会影响您采用哪种方法。请参阅此 SO 解释和骷髅箱链接以获取解释。 ***.com/questions/47903/… 我知道 TCP 和 UDP 的区别。我的观点是,如果我发送到 127.0.0.1,无论使用何种协议,都应该有 0 的数据丢失,除非故意明确引入一些数据丢失。 UDP数据丢失来自于它是一个面向消息的协议,没有包序列的概念。在本地运行时均不适用。 你怎么说它在本地不适用? Physics 说数据包没有理由乱序到达。在任何网络上,无论是否本地,节点之间只有一条路径,在数据包 B 之前发送的数据包 A 将始终首先到达目的地。此外,发送到 127.0.0.1 不应该(并且在我曾经使用过的所有东西下,不)依赖于任何网络。为了证明这一点,只需拔掉所有网线并关闭所有无线电——您仍然可以连接到 127.0.0.1/localhost 并发送数据。【参考方案5】:

我不知道是什么让您期望 UDP 丢弃数据包的百分比低于 1%。

话虽如此,基于RFC 1122(参见section 3.3.2),保证不被拆分为多个IP 数据报的最大缓冲区大小为576 字节。较大的 UDP 数据报可能会被传输,但它们可能会被拆分为多个 IP 数据报,以便在接收端重新组合。

我想造成您看到的丢包率的一个原因是,如果作为大型 UDP 数据报一部分的一个 IP 数据包丢失,整个 UDP 数据报将迷失。而且您计算的是 UDP 数据报,而不是 IP 数据包。

【讨论】:

我希望丢包...通过网络。但是,我正在发送到本地主机。我预计不会丢弃或丢弃很少的 IP 数据包。

以上是关于什么会导致 UDP 数据包在发送到 localhost 时被丢弃?的主要内容,如果未能解决你的问题,请参考以下文章

Linux网络 - 数据包在内核中接收和发送的过程(转)

在一个线程中定期发送许多 UDP 数据报有时会导致微包突发

什么可能导致 UDP 数据包计时不一致?

什么会导致无法为 UDP 数据报计算 IP 标头校验和?

通过 UDP 发送 RC4 加密数据会导致解密明文发生变化

UDP通信_ DatagramSocket 实现_客户咨询