【问题标题】:Delay increases sometimes when sending buffer via UDP socket通过 UDP 套接字发送缓冲区时延迟有时会增加
【发布时间】:2018-06-08 17:32:35
【问题描述】:

我开发了一个 MPEG-ts Streamer。它从文件中读取数据包并以正确的速度将它们发送给接收者。

现在一切正常,除了我经常有一些滞后。我已经在我的代码中搜索了所有可能的错误。我已经在性能方面优化了我的程序。

现在我记录了sendto() 函数发送数据包所花费的时间,我还记录了数据包应该发送的时间和发送时间之间的差异。

我注意到每次数据包都比平均时间晚很多,sendto() 发送前一个数据包的时间也比正常时间长得多。

这表明我是 sendto() 每次发送数据包需要更长的时间时都会导致这些滞后。我正在使用 UDP 套接字。

我可能对套接字做错了吗?套接字缓冲区是否已满并且发送数据包实际上需要更长的时间?还是我错过了什么?有没有办法加速套接字或使它在发送之前不完全填满它的缓冲区?

由于这是为了流式传输视频,我在很大程度上取决于性能,主要用于 HD,因为数据包的数量更多,所以延迟发生的频率更高。

【问题讨论】:

  • 您是从网络对等方获得响应,还是您只是发送而对等方只是接收?
  • 发送缓冲区很可能已满,您应该测量一下您使用了多少带宽,以及您有多少可用带宽。如果您发布了一些您发现的时间,以及更多关于您发送的数据量和速率、数据包大小等的信息,这可能会有所帮助。也可以使用wireshark 查看内容——您要确保自己没有坚持例如arp 超时或触发大量 ICMP 回复。 (请注意,当接收缓冲区已满时,您很容易丢失 UDP 数据包,但如果发送缓冲区已满,您的程序会阻塞。)

标签: c linux network-programming


【解决方案1】:

sendto() 屏蔽的原因只有两个:

  • 您的传出 UDP 缓冲区已满,需要等到空间释放完毕
  • CPU 调度程序只是决定在一段时间内执行其他操作,就像任何系统调用一样。

检查输出缓冲区的大小:

int buff_size;
int len = sizeof(buff_size);

err = getsockopt(s,SOL_SOCKET,SO_SNDBUF,(char*)&size,&len);

一些 linux 系统默认发送缓冲区非常小(只有几千字节),因此您可能需要将其设置为更大的值。

如果缓冲区更大并且sendto() 无论如何都会短暂阻塞(究竟多长时间?)那么这可能只是做生意的成本。即使在 LAN 上,当然在 WAN 上,延迟也会有很大差异。

更新

增加 UDP 缓冲区大小的系统限制:

sysctl -w net.core.wmem_max=1048576
sysctl -w net.core.rmem_max=1048576

您可以通过将以下行添加到/etc/sysctl.conf 来使其永久化:

net.core.wmem_max=1048576
net.core.rmem_max=1048576

要在您的应用程序中利用此功能,您需要使用setsockopt()

int len, trysize, gotsize;
len = sizeof(int);
trysize = 1048576+32768;
do {
   trysize -= 32768;
   setsockopt(s,SOL_SOCKET,SO_SNDBUF,(char*)&trysize,len);
   err = getsockopt(s,SOL_SOCKET,SO_SNDBUF,(char*)&gotsize,&len);
   if (err < 0) { perror("getsockopt"); break; }
} while (gotsize < trysize);
printf("Size set to %d\n",gotsize);

SO_RCVBUF 重复相同的操作。循环很重要,因为许多系统会默默地强制执行一个最大值,该最大值小于您使用sysctl 设置的最大值,并且当setsockopt() 失败时,它会保持先前的值不变。所以你必须尝试许多不同的值,直到你得到一个坚持。不测试(gotsize == trysize) 也很重要,因为在某些系统上设置的结果实际上与您请求的结果不同。

【讨论】:

  • 感谢您的回答。但我有个问题。有没有办法在 linux shell 上找到发送缓冲区的大小?
  • 您可以使用sysctl net.core.wmem_maxsysctl net.core.rmem_max 找到上限(允许设置的最大进程)。您可以使用sysctl net.core.wmem_defaultnet.core.rmem_default 找到默认值。 但是,我注意到一些 linux 系统表现出一些怪癖,例如没有设置您请求的确切值,设置的值是您请求的两倍或一半,或者使用与 sysctl 所说的不同的边界。它似乎因内核版本和发行版而异,所以请期待意外。
  • 谢谢。我的结果是 net.core.wmem_max = 131071 和 net.core.wmem_default = 126976。那是字节吗?高清流媒体够大吗?这可能是我的问题吗?
  • 这是字节,这是最常见的 linux 结果,在我看来,它不足以做任何大容量的事情。 _max 绝对没有理由小于 1 兆字节。 _default 应该不理会,因为小批量进程不需要更多,但大批量进程应该免费提供给 setsockopt() 他们需要的东西。不幸的是,找出缓冲区大小是否在 linux 中引起问题的唯一方法是提高它并查看是否有帮助。
  • 好的,谢谢。你能告诉我如何提高那个最大值吗?非常感谢。
【解决方案2】:

与 TCP 不同,UDP 不缓冲数据。源代码肯定有助于更好地理解事物。你发送的数据包有多大?在 UDP 中,最好将有效负载保持在 MTU 的大小(1500 字节)

【讨论】:

  • 您好,很遗憾,我不能发布源代码,因为我不允许这样做。我正在单独发送 MPEG-TS 数据包(188 字节)。
  • 并不是说它应该解释您的延迟,但是 188 字节对于有效负载来说有点低,因为 IP/UDP 开销变得不可忽略。引入更多缓冲并在每个数据报中发送至少 3 或 4 个 MPEG 数据包可能会更好
  • 我不明白的是,它的工作方式非常好。真的只是有时发送缓冲区需要更长的时间,但它会再次快速发送。
  • 参考 m0skit0 的评论,使用 188 字节的数据包会产生更多的系统调用,这可能会使您的代码对偶尔的调度程序延迟更加敏感。在流媒体中,与视频会议相反,您最好通过发送尽可能接近 MUT 大小的数据包来优化网络利用率
  • 操作系统/内核肯定有用于传入和传出 (UDP) 数据包的套接字缓冲区。 (虽然没有流量控制,所以慢速接收器不会像 TCP 一样传播回发送器。)
【解决方案3】:

没有代码是不可能的,但这里有一些提示:

  • 您的代码可能运行良好,而这些延迟是由操作系统调度程序引起的。
  • 通常,过早优化代码实际上会导致性能下降。优化是最后一步。

您可以尝试“更好地”处理您的流程。如果这提高了性能,那就是操作系统调度程序的问题。

【讨论】:

  • 我所做的优化至关重要,因为我没有正确缓冲数据包。现在经过优化后,它可以完美运行,除了偶尔的滞后
  • 这不是优化。这是一个错误修复;)
  • lol 抱歉,你猜对了,呵呵 :) 虽然固定程序总是被优化:P
  • 调度程序引起的问题很常见。 Linux 默认调度算法不适合软 RT 性能,jitter 与此密切相关。在 fork 或创建线程时应该使用 RT 调度。
【解决方案4】:

[EDIT] 将接收器替换为具有缓冲区的东西。或者,也许尝试发送比接收者真正需要的更多的数据——它确实应该已经有一个缓冲区。我怀疑世界上是否有人制造机顶盒并且不知道存在网络延迟并且还没有破产。 p>

根据您的解释,我怀疑原因在于您的代码。速度变慢的原因有很多:

  • 由于您正在进行 I/O,操作系统可能会决定提供另一个进程控制。
  • 网络缓冲区可能已满
  • 网络可能拥塞
  • 病毒扫描程序可能对您正在读取的相同数据感兴趣并窃取过多周期。
  • 如果您使用的是 Windows:Aero 桌面会呈现某些内容(显卡的 IRQ 通常比网卡的优先级更高)。

结论:你的问题是接收器没有缓冲区。确实有很多原因会导致 UDP 数据包的发送延迟,并且所有这些原因都不受您的代码的控制。


原答案

UDP 不是一个可靠的协议。无法保证数据包及时到达接收方。 UDP 的开销只是比 TCP 小,但这是以可靠性为代价的。

通常的解决方案是发送比客户端需要的更多的数据。例如,您在全速发送前 10 秒。这允许客户端缓存一些数据以延迟播放。

您还应该更改客户端以每隔几秒将缓存流的长度发送回服务器。然后,服务器可以计算要全速发送多少数据包以保持客户端的缓存被填满。

【讨论】:

  • 问题是它不是一个软件客户端而是一个机顶盒。其他的事情是,这并不总是发生。它只是偶尔发生,我不明白为什么在某些时候 sendto() 需要比平时更长的时间
  • 你怎么知道sendto() 需要更长的时间?你量过吗?怎么样?
  • struct timespec stimeBefore;结构时间规范时间之后; clock_gettime(CLOCK_REALTIME, &stimeBefore);这就是我测量它的方式 sendto(...); clock_gettime(CLOCK_REALTIME, &stimeAfter); fprintf(log,"发送时间 = %d\n",((((stimeAfter.tv_sec * 1000000000) + stimeAfter.tv_nsec) - ((stimeBefore.tv_sec * 1000000000) + stimeBefore.tv_nsec)) / 1000));
猜你喜欢
  • 2012-03-03
  • 2016-11-19
  • 2023-03-31
  • 2010-11-09
  • 2014-08-19
  • 1970-01-01
  • 2016-12-18
  • 2017-08-26
  • 2018-11-08
相关资源
最近更新 更多