C 通过UDP套接字发送缓冲区时,延迟有时会增加

C 通过UDP套接字发送缓冲区时,延迟有时会增加,c,linux,network-programming,C,Linux,Network Programming,我开发了一个MPEG ts流媒体播放器。它从文件中读取数据包,并以正确的速度将数据包发送给接收方 现在一切都很好,除了我经常有一些滞后。我已经搜索了代码中所有可能的错误。我已经在性能方面对我的程序进行了相当多的优化 现在,我保存了一个日志,记录了sendto()函数发送数据包所需的时间,还记录了数据包应该发送的时间和发送的时间之间的差异 我注意到,每次数据包都比平均时间晚很多,发送前一个数据包所用的时间也比正常情况高很多 这表明每次发送数据包花费的时间更长时,都是sendto()造成了这些延迟。

我开发了一个MPEG ts流媒体播放器。它从文件中读取数据包,并以正确的速度将数据包发送给接收方

现在一切都很好,除了我经常有一些滞后。我已经搜索了代码中所有可能的错误。我已经在性能方面对我的程序进行了相当多的优化

现在,我保存了一个日志,记录了
sendto()
函数发送数据包所需的时间,还记录了数据包应该发送的时间和发送的时间之间的差异

我注意到,每次数据包都比平均时间晚很多,发送前一个数据包所用的时间也比正常情况高很多

这表明每次发送数据包花费的时间更长时,都是
sendto()
造成了这些延迟。我正在使用UDP套接字

我是不是把插座弄错了?是否有可能套接字缓冲区已满,并且发送数据包实际需要更长的时间?还是我遗漏了什么?是否有一种方法可以加速套接字或使其在发送之前不完全填满缓冲区


由于这意味着要流式传输视频,因此我非常依赖于性能,主要是对于HD,由于数据包的数量要高得多,延迟发生的频率更高。

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

  • 您的代码可能工作正常,而这些延迟是由OS调度程序造成的
  • 通常,过早优化代码可能会导致性能下降。优化是最后一步

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

[EDIT]将接收器更换为具有缓冲区的接收器。或者尝试发送比接收器真正需要的更多的数据-它确实应该已经有一个缓冲区了。我怀疑世界上是否有人制造机顶盒而不知道存在网络滞后而尚未破产

从你的解释来看,我怀疑原因是否在你的代码中。速度较慢的原因有很多:

  • 由于您正在执行I/O,操作系统可能会决定提供另一个进程控制
  • 网络缓冲区可能已满
  • 网络可能很拥挤
  • 病毒扫描程序可能对您正在读取的相同数据感兴趣,并且窃取了太多的周期
  • 如果您在Windows上:Aero桌面渲染某些内容(图形卡的IRQ通常比网卡的IRQ优先级更高)
结论:您的问题是接收器没有缓冲区。确实有很多原因会导致UDP数据包的发送延迟,并且所有这些原因都不在代码的控制之下


原始答案

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

通常的解决方案是发送比客户端需要的更多的数据。举个例子,你在油门全开时发送前10秒。这允许客户端缓存一些数据,以便在延迟期间播放


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

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

只有两个原因导致
sendto()
阻塞:

  • 传出UDP缓冲区已满,需要等待空间释放
  • CPU调度器只是决定暂时做一些其他的事情,就像它对任何系统调用所做的那样
检查传出缓冲区的大小:

int buff_size;
int len = sizeof(buff_size);

err = getsockopt(s,SOL_SOCKET,SO_SNDBUF,(char*)&size,&len);
一些linux系统默认使用非常小的发送缓冲区(只有几千字节),因此您可能需要将其设置为更大的缓冲区

如果缓冲区更大,并且
sendto()
只是短暂地阻塞(具体多长时间?),那么这可能只是做生意的成本。即使在局域网上,当然在广域网上,延迟也会有很大的变化

更新

要增加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;
做{
trysize-=32768;
setsockopt(s,SOL_SOCKET,SO_SNDBUF,(char*)和trysize,len);
err=getsockopt(s,SOL_SOCKET,SO_SNDBUF,(char*)和gotsize,&len);
如果(err<0){perror(“getsockopt”);break;}
}而(gotsize

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

您是否从网络对等方收到响应,或者您只是发送,而对等方刚刚接收?发送缓冲区肯定已满,您应该对使用的带宽和可用带宽进行测量。如果你公布一些你发现的时间,以及更多关于da数量和速率的信息,可能会有所帮助
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);