在 linux 中,如果我尽可能快地调用 send() 为什么会丢失 UDP 数据包?
In linux, why do I lose UDP packets if I call send() as fast as possible?
隐含的问题是:如果 Linux 在套接字的发送缓冲区已满时阻止 send()
调用,为什么会有任何丢失的数据包?
更多详情:
我用 C 编写了一个小实用程序,以尽快将 UDP 数据包发送到单播地址和端口。我每次发送一个 1450 字节的 UDP 负载,第一个字节是一个计数器,每个数据包递增 1。我 运行 它在带有 1Gb 网卡(=相当慢)的桌面 PC 上的 VirtualBox 中的 Fedora 20 上。
然后我编写了一个小实用程序来从给定端口读取 UDP 数据包,它根据自己的计数器检查数据包的计数器,如果它们不同则打印一条消息(即 1 个或多个数据包已丢失)。我 运行 它在带有 1Gb 以太网网卡(=超快)的 Fedora 20 双至强服务器上。它确实显示了很多丢失的数据包。
两台机器都在本地网络上。我不知道它们之间的确切跳数,但我认为它们之间的路由器不会超过 2 个。
我尝试过的事情:
- 在每个
send()
之后添加延迟。如果我将延迟设置为 1 毫秒,则不会再有任何数据包丢失。 100us 的延迟将开始丢包。
- 使用
setsockopt()
将接收套接字缓冲区大小增加到 4MiB。这没有任何区别...
请赐教!
- 如果过载,您提到的两个路由器中的任何一个都可能丢包,
- 并且在某些情况下(例如过载),接收 PC 也可能会丢弃或丢失数据包。
即使 send()
在发送缓冲区已满时阻塞(前提是您没有在套接字上设置 SOCK_NONBLOCK
以将其置于非阻塞模式),接收方仍然必须很快足以处理所有传入的数据包。如果接收方或任何中间系统比发送方慢,则使用 UDP 时数据包将丢失。请注意,slower 不仅适用于网络接口的速度,还适用于整个网络堆栈以及用户空间应用程序。
在您的情况下,接收方很可能正在接收所有数据包,但在用户空间中无法足够快地处理它们。您可以通过 tcpdump
或 wireshark
.
记录和分析您的流量来检查这一点
如果您不想丢失数据包,请切换到 TCP。
对于 UDP,SO_SNDBUF
套接字选项仅限制您可以发送的数据报的大小。与 TCP 一样,没有显式限制发送套接字缓冲区。当然,在内核中将帧排队到网卡。
换句话说,send(2)
可能会在不返回错误的情况下丢弃您的数据报(查看手册页底部对 ENOBUFS
的描述)。
然后数据包可能会在路径上的几乎任何地方被丢弃:
- 发送网卡没有可用的硬件资源来处理请求,帧被丢弃,
- 中间路由设备没有可用缓冲区space或实施某种拥塞避免算法,丢弃数据包,
- 接收网卡无法接受给定速率的以太网帧,一些帧被忽略。
- reader 应用程序没有足够的套接字接收缓冲区 space 来适应流量高峰,内核丢弃数据报。
不过从你所说的来看,这听起来很可能是虚拟机无法以高速率发送数据包。使用 tcpdump(1)
或 wireshark(1)
尽可能靠近源头嗅探电线,并检查您的序列号 - 它会告诉您是否是发件人应该受到指责。
正如上述发帖人之一所说,UDP 是一种简单的数据报协议,不保证传送。要么是因为本地机器,网络上的设备等。这就是为什么许多当前的开发人员会建议,如果您想要可靠性,则切换到 TCP 的原因。但是,如果您真的想坚持使用 UDP 协议并且有很多正当理由这样做,您将需要找到一个可以帮助您保证交付的库。寻找 SS7 项目,尤其是在使用 UDP 传输语音、数据和信令信息的电话 API 中。对于您的唯一用途应用程序,我可以建议使用 enet UDP 库。http://enet.bespin.org/
隐含的问题是:如果 Linux 在套接字的发送缓冲区已满时阻止 send()
调用,为什么会有任何丢失的数据包?
更多详情: 我用 C 编写了一个小实用程序,以尽快将 UDP 数据包发送到单播地址和端口。我每次发送一个 1450 字节的 UDP 负载,第一个字节是一个计数器,每个数据包递增 1。我 运行 它在带有 1Gb 网卡(=相当慢)的桌面 PC 上的 VirtualBox 中的 Fedora 20 上。
然后我编写了一个小实用程序来从给定端口读取 UDP 数据包,它根据自己的计数器检查数据包的计数器,如果它们不同则打印一条消息(即 1 个或多个数据包已丢失)。我 运行 它在带有 1Gb 以太网网卡(=超快)的 Fedora 20 双至强服务器上。它确实显示了很多丢失的数据包。
两台机器都在本地网络上。我不知道它们之间的确切跳数,但我认为它们之间的路由器不会超过 2 个。
我尝试过的事情:
- 在每个
send()
之后添加延迟。如果我将延迟设置为 1 毫秒,则不会再有任何数据包丢失。 100us 的延迟将开始丢包。 - 使用
setsockopt()
将接收套接字缓冲区大小增加到 4MiB。这没有任何区别...
请赐教!
- 如果过载,您提到的两个路由器中的任何一个都可能丢包,
- 并且在某些情况下(例如过载),接收 PC 也可能会丢弃或丢失数据包。
即使 send()
在发送缓冲区已满时阻塞(前提是您没有在套接字上设置 SOCK_NONBLOCK
以将其置于非阻塞模式),接收方仍然必须很快足以处理所有传入的数据包。如果接收方或任何中间系统比发送方慢,则使用 UDP 时数据包将丢失。请注意,slower 不仅适用于网络接口的速度,还适用于整个网络堆栈以及用户空间应用程序。
在您的情况下,接收方很可能正在接收所有数据包,但在用户空间中无法足够快地处理它们。您可以通过 tcpdump
或 wireshark
.
如果您不想丢失数据包,请切换到 TCP。
对于 UDP,SO_SNDBUF
套接字选项仅限制您可以发送的数据报的大小。与 TCP 一样,没有显式限制发送套接字缓冲区。当然,在内核中将帧排队到网卡。
换句话说,send(2)
可能会在不返回错误的情况下丢弃您的数据报(查看手册页底部对 ENOBUFS
的描述)。
然后数据包可能会在路径上的几乎任何地方被丢弃:
- 发送网卡没有可用的硬件资源来处理请求,帧被丢弃,
- 中间路由设备没有可用缓冲区space或实施某种拥塞避免算法,丢弃数据包,
- 接收网卡无法接受给定速率的以太网帧,一些帧被忽略。
- reader 应用程序没有足够的套接字接收缓冲区 space 来适应流量高峰,内核丢弃数据报。
不过从你所说的来看,这听起来很可能是虚拟机无法以高速率发送数据包。使用 tcpdump(1)
或 wireshark(1)
尽可能靠近源头嗅探电线,并检查您的序列号 - 它会告诉您是否是发件人应该受到指责。
正如上述发帖人之一所说,UDP 是一种简单的数据报协议,不保证传送。要么是因为本地机器,网络上的设备等。这就是为什么许多当前的开发人员会建议,如果您想要可靠性,则切换到 TCP 的原因。但是,如果您真的想坚持使用 UDP 协议并且有很多正当理由这样做,您将需要找到一个可以帮助您保证交付的库。寻找 SS7 项目,尤其是在使用 UDP 传输语音、数据和信令信息的电话 API 中。对于您的唯一用途应用程序,我可以建议使用 enet UDP 库。http://enet.bespin.org/