- 使用select()一次检查FD_SETSIZE个套接字,然后迭代所有套接字以接收数据。(这不需要为每个套接字返回两次调用recv吗?MSG_PEEK分配一个缓冲区,然后再次recv(),这与#3相同)
- 使用select()逐个检查一个套接字。(这也不像#3吗?它需要两次调用recv。)
- 使用recv()和MSG_PEEK逐个套接字接收数据,分配缓冲区,然后再次调用recv()。这样做会更好,因为我们可以跳过所有对select()的调用吗?还是一个recv()的开销太大了?
谢谢。
FD_SETSIZE
通常是1024,因此您可以一次检查所有500个连接。然后,您将仅在已准备好的连接上执行两个recv
调用-例如,在非常繁忙的系统中,每次循环大约有半打这样的连接。使用其他方法,您需要进行大约500个额外的系统调用(在许多数百个套接字上执行巨大数量的“失败”的recv
或select
调用,这些套接字在任何给定时间都不会准备好!)。
此外,使用第一种方法,您可以阻塞直到至少一个连接准备就绪(在这种情况下没有开销,这在并不那么繁忙的系统中并不罕见)-使用其他方法,您需要“轮询”,即持续不断地翻转,无益地消耗大量CPU(或者,如果您在每次检查循环之后休眠一段时间,那么尽管系统根本不忙碌,但您仍然需要延迟响应)。-)。
这就是为什么我认为轮询是一种反模式:经常使用,但仍然具有破坏性。有时您绝对没有替代方案(这基本上告诉您,您必须与设计非常糟糕的系统进行交互-遗憾的是,在这个不完美的生活中,有时您确实需要!),但是当任何体面的替代方案存在时,仍然进行轮询实际上是一种非常糟糕的设计实践,应该避免。
使用select()一次检查FD_SETSIZE个套接字,然后迭代所有套接字以接收数据。(这不需要为每个返回的套接字调用两次recv吗?MSG_PEEK分配缓冲区,然后再次recv()它,这与#3相同)
我相信您正在仔细构建仅包含当前连接描述符的fd集合……?然后,您遍历该集合,并仅对具有读取或异常/错误条件(后者差异在于BSD和Windows实现之间)的套接字发出recv()。虽然从功能上讲可以(并且在概念上可以说是优雅的),但在大多数实际应用程序中,您不需要在recv之前进行窥视:即使您不确定消息大小并知道可以从缓冲区中窥视它,您也应该考虑是否可以:
使用select()逐个检查套接字。 (这难道不也像#3吗?它需要两次调用recv。)
一点也不好。如果您保持单线程,则需要在select上放置0超时值,并通过监听和客户端描述符不断旋转。非常浪费CPU时间,并且会极大地降低延迟。
使用带有MSG_PEEK 的recv()逐个套接字,分配缓冲区,然后再次调用recv()。这样做是否更好,因为我们可以跳过所有对select()的调用?还是一个recv()调用的开销太大?
(忽略不推荐使用MSG_PEEK)-你如何知道在哪个套接字上使用MSG_PEEK或recv()?如果你是单线程的,那么要么你会在第一次peek/recv尝试时阻塞,要么你会使用非阻塞模式,然后通过所有描述符旋转来希望peek/recv会返回一些内容。这很浪费时间。