select()和recv与MSG_PEEK之间的效率。异步

时间:2010-09-08 02:06:59

标签: c++ sockets asynchronous

我想知道在检查传入数据时(异步)最有效的方法。假设我有500个连接。我有3个场景(我能想到):

  1. 使用select()一次检查FD_SETSIZE套接字,然后迭代所有套接字以接收数据。 (这不需要为每个返回的套接字调用两次recv吗?MSG_PEEK分配一个缓冲区然后recv()它再次与#3相同)
  2. 使用select()一次检查一个套接字。 (这也不会像#3一样吗?它需要两次调用recv。)
  3. 一次使用recv()和MSG_PEEK一个套接字,分配缓冲区然后再次调用recv()。这会不会更好,因为我们可以跳过select()的所有调用?或者是一个recv()调用过多的开销?
  4. 我已经将情况编码为1和2,但我不确定使用哪种情况。对不起,如果我有点不清楚。

    由于

3 个答案:

答案 0 :(得分:3)

FD_SETSIZE通常为1024,因此您可以一次检查所有500个连接。然后,您只会对那些已准备好的执行两次recv次呼叫 - 例如,对于一个非常繁忙的系统,每次都会有六个这样的呼叫。使用其他方法,您需要大约500个系统调用(在准备好的数百个套接字上执行的大量“失败”recvselect调用在任何给定的时间! - )。

此外,使用方法1,您可以阻止,直到至少有一个连接 准备就绪(在这种情况下没有开销,这在不那么繁忙的系统中并不罕见) - - 使用其他方法,你需要进行“轮询”,即搅拌,持续不断地将大量的CPU烧掉,没有任何好的目的(或者,如果你在每次循环之后睡了一会儿)检查,然后你会有一个延迟响应尽管系统没有忙 - eep! - 。)。

这就是为什么我认为轮询 - 模式:经常使用,但仍具有破坏性。有时候你绝对没有其他选择(这基本上告诉你,你必须与设计非常糟糕的系统进行交互 - 唉,有时候你在这个不完美的生活中必须! - ),但是当体面的替代确实存在,然而进行投票确实是一个非常糟糕的设计实践,应该避免。

答案 1 :(得分:3)

你可以简单地在3场景中进行一些效率模拟:

情景A(0/500传入数据)

  • 对于解决方案#1,您只需调用单个select()
  • 对于解决方案#2,您需要500 select()
  • 对于解决方案#3,您需要500 recv()

情景B(250/500传入数据)

  • 针对解决方案#1,单select() +(500 recv()
  • for solution#2,5,500 select() +(500 recv()
  • for solution#3,750 recv()

**假设跳过没有缓冲区大小的套接字@没有传入数据
答案显而易见:)

答案 2 :(得分:1)

  

...检查时效率最高   传入数据(异步)。让我们   说我有500个连接。我有3个   场景(我能想到):

     

使用select()检查FD_SETSIZE   一次插座,然后迭代   所有这些都是为了接收数据。   (这不需要两个电话   返回每个套接字的recv? MSG_PEEK分配缓冲区然后再次recv()它将与#3相同

我相信你只是用目前连接的描述符仔细构建你的fd集......?然后迭代该集合,并仅针对具有读取或异常/错误条件的那些(后者区别在于BSD和Windows实现之间)发出recv()。虽然它在功能上是可以的(并且在概念上可以说是优雅的),但在大多数真实世界的应用程序中,您不需要在恢复之前查看:即使您不确定消息大小并且知道您可以从缓冲区查看它,应该考虑一下你是否可以:

  • 以块的形式处理消息(例如,读取任何好的工作单元 - 可能是8k,处理它,然后读取下一个< = 8k进入同一个缓冲区......)
  • 读入一个足以容纳大多数/所有邮件的缓冲区,并且只有在发现邮件不完整时才动态分配更多
  

使用select()一次检查一个套接字。 (这不会像#3那样吗?它需要两次调用recv。)

一点也不好。如果你保持单线程,你需要在select上放一个0超时值,并通过listenig和client描述符疯狂地旋转。非常浪费CPU时间,并且会大大降低延迟。

  

一次使用recv()和MSG_PEEK一个套接字,分配缓冲区然后再次调用recv()。这会不会更好,因为我们可以跳过select()的所有调用?或者是一个recv()调用过多的开销?

(忽略尝试避免使用MSG_PEEK更好) - 你怎么知道MSG_PEEK或recv()的哪个套接字?再次,如果你是单线程,那么你要么阻止第一次peek / recv尝试,要么你使用非阻塞模式,然后通过所有描述符疯狂旋转,希望窥视/ recv将返回一些东西。浪费的。

所以,坚持1或转移到多线程模型。对于后者,最简单的方法是让监听线程循环调用accept,每次accept产生一个新的客户端描述符时,它应该生成一个新线程来处理连接。这些客户端连接处理线程可以简单地阻塞在recv()中。这样,操作系统本身就会响应事件来监视和唤醒线程,您可以相信它会合理有效。虽然这个模型听起来很简单,但你应该知道多线程编程还有很多其他的复杂问题 - 如果你还不熟悉它,你可能不想尝试在套接字I / O的同时学习它。 / p>