我们有客户端应用程序来侦听 UDP 多播源并处理传入数据。它是可移植的并且可以在 Windows 和 Linux 上运行。主处理循环使用
select()
等待数据,通常是一两个UDP非阻塞套接字:
while(!stopRequested)
{
fdset io;
FD_ZERO(&io);
FD_SET(sock, &io);
timeval waitInterval = { 0 };
waitInterval.tv_usec = 10000; // 10 milliseconds
int r = select(sock + 1, &io, NULL, NULL, &waitInterval);
if(r == 0) // Process timeout
else // Data or error processing
}
代码运行得很好,但是当没有可用数据时,超时准确性存在问题。 我们测量了在几秒钟的保证空闲(没有发送数据)期间实际花费在
select()
内的时间,分布如下:
<1 usec : 170 time(s)
<2000 usec : 1 time(s)
<10000 usec : 11973 time(s)
<12000 usec : 6558 time(s)
<15000 usec : 64 time(s)
<20000 usec : 47 time(s)
没有错误,
select()
总是返回0。所以我们可以看到,有几种情况(170次)select()
几乎立即返回,而不等待任何超时。
所以问题是为什么在某些情况下不遵守超时? Windows (Win7 x64) 和 Linux (CentOS/RHEL6.0 x64) 都获得了类似的结果。
此外,当使用多线程时,事情变得更糟。当两个线程执行上面的代码时(都为同一个套接字调用
select()
,但fd_set
和waitInterval
是本地对象),select()
内部的时间分布是这样的(对于每个线程):
<1 usec : 13800827 time(s)
<10 usec : 1639 time(s)
<100 usec : 8660 time(s)
<1000 usec : 16 time(s)
<12000 usec : 768 time(s)
<15000 usec : 39 time(s)
看起来
select()
几乎从不考虑超时,但在并发调用中立即返回 0。
对于这种令人困惑的行为有什么解释吗?检查了不重新初始化
fd_set
和 timeout
参数的常见陷阱,但事实并非如此。
实际上
select()
没有任何问题,它正确地尊重超时。测量是错误的 - 我测量的不是 select()
调用本身,而是实际检查 fd_set
并在为空时跳过 select()
调用的包装函数。随时<10000 usec aren't related to select()
致电。
这个问题可能会因为不相关而被删除。