套接字select()与非阻塞recv的区别

18
我看到了几篇文章比较了select()和poll()或epoll(),也看到了很多指南讨论如何使用select()来处理多个套接字。但是,我找不到一个没有使用select()的非阻塞recv()调用的比较。如果只有1个要读取数据的套接字和1个要写入数据的套接字,是否有理由使用select()调用呢?recv()方法可以设置为不阻塞并在没有数据可用时返回错误(WSAEWOULDBLOCK),那么当您没有其他套接字需要检查时,为什么还要调用select()呢?非阻塞的recv()调用会慢很多吗?

这可能是你实现的所有细节。你试过了吗? - Carl Norum
当没有数据可以持续读取时,你打算怎么做?无限循环吗?还是使用select、poll或epoll?无论是阻塞还是非阻塞,你都需要以某种方式等待数据。 - goji
是的,就像@Troy所说的那样,这种方式会实现主动等待 - 这意味着当没有东西可读时,处理器将毫不留情地运行。 - zoska
3个回答

10
您不希望在没有其他等待套接字数据的手段的情况下调用非阻塞的接收函数,因为此时轮询将无限耗费CPU时间。
如果您没有其他要检查的套接字,也没有同一线程中的其他操作可做,那么阻塞调用读取函数可能是最有效的解决方案。尽管在这种情况下,考虑效率往往是过早进行的优化。
这类考虑只有当套接字计数增加时才会引起注意。
非阻塞调用仅在处理单个线程上的多个套接字时才更快。

1
从您的回复和下面的回复中,我理解循环recv()更加CPU密集,这让我怀疑在循环中使用select()实际上会挂起线程,是我想知道的区别。此外,如果这是真的,我会怀疑它只会挂起一段时间,这将使select成为一个阻塞调用?了解到这一点后,您如何建议使用2个套接字线程(1个用于输入,1个用于输出),其中您需要它们都以最大速度处理,但仅当队列中有新数据可用时,输出才会有意义? - Gren Meera
1
我个人认为这本书是套接字编程的圣经:http://www.amazon.com/UNIX-Network-Programming-Networking-Sockets/dp/013490012X - goji
1
很不幸,我找到的所有关于读写select()线程的示例都使用timeval来阻塞select(),这并不能尽可能快地进行读写。你需要在再次调用它之前等待select()返回。如果你给select()一个零时间值,我理解你的意思是这样做?在你的循环中给出一个零时间值是否仍然比使用非阻塞调用更加友好于CPU,因为你将以相同的速率调用它? - Gren Meera
1
那些都在我的理解范围内,我相信我卡在了另外一件事情上。例如,我有数据在我的队列中需要写入。我调用select()。它很可能会返回一个写标志,我调用send(),一切都很愉快。现在我的写队列里没有任何东西,但我仍然希望读取。我在循环中再次调用select。现在它将阻塞,直到有数据可供读取,但是我可能会得到更多的数据,我希望将其发送到我的发送队列中。不幸的是,我被阻塞在select中,直到select()超时或我收到数据之前,我无法发送数据。 - Gren Meera
2
更多背景信息:我的问题是我必须尽可能快地读取从套接字传入的数据,并且当可用数据在我的进程中时,我需要尽可能快地写入。这是在一个线程上进行的,并且它使用略旧的库,在不支持信号的机器上也不会支持。我最好的选择似乎是使用 select 和非阻塞调用。这就是我寻求建议的地方。 - Gren Meera
显示剩余3条评论

6
如果没有数据可用,并且您使用非阻塞IO,则recv()将立即返回。那么程序应该怎么办?您需要在循环中调用recv(),直到数据变为可用-这只是出于几乎没有原因而使用CPU。
recv()上旋转并烧毁CPU非常不理想;您更希望进程等待数据变为可用并被唤醒;这就是select()/poll()和类似函数的作用。
而在循环中使用sleep()以避免烧毁CPU也不是一个好的解决方案。这会导致处理过程的高延迟,因为程序无法在数据可用时立即处理数据。

5
select()等函数可以让您设计工作流程,使一个套接字的慢速不会影响您服务另一个套接字的速度。假设数据从接收套接字快速到达,并且您希望尽快接受和存储在内存缓冲区中。但是发送套接字很慢。当您填满操作系统的发送缓冲区并且send()返回EWOULDBLOCK时,您可以发出select()以在接收和发送套接字上等待。select()将在到达新数据或释放一些缓冲区并且可以向发送套接字写入更多数据之一的情况下结束。

当然,select()的更实际用途是当您需要从多个套接字读取和/或写入数据时,或者必须在两个套接字之间双向传递数据时。

事实上,select()告诉您何时已知下一个读取或写入操作将成功,因此,如果您只在允许的情况下尝试读取和写入,则即使没有使套接字非阻塞,程序几乎也能工作!但这仍然是不明智的,因为存在边缘情况,即使select()报告套接字为“就绪”,下一个操作仍可能阻塞。

另一方面,几乎从不建议使套接字非阻塞并且不使用select(),原因请参见@Troy的解释。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接