IOCP - 发布重叠或读取数据包?

时间:2013-02-16 14:10:08

标签: c++ sockets networking tcp iocp

我应该读取前9个字节,其中应包括协议和数据包的传入大小。

当完成端口返回9个字节时哪个更好? (表现/良好做法或美学明智)

  1. 在套接字上发布另一个重叠的读取,这次是数据包的大小,以便在下次完成时接收它?
  2. 使用阻塞套接字在例程中读取整个数据包,然后发布另一个与9字节重复的recv?
  3. 读入块(决定大小)说 - 4096并有一个计数器来继续读取每个重叠的完成,直到读取数据(比如它将完成12次直到读取所有数据包)。

2 个答案:

答案 0 :(得分:1)

答案取决于您使用的基础架构。一般来说,最好的事情是什么都不做。我知道这听起来很奇怪,所以让我解释一下。当操作系统与NIC通信时,它通常至少有一对RX / TX环形缓冲区,并且在商用硬件的情况下,可能通过PCIe总线与设备通信。在PCIe总线之上有一个DMA引擎,使得NIC可以在不使用CPU的情况下从/向主机内存进行读写。换句话说,当NIC处于活动状态时,它将始终自行读写数据包,并且CPU干预最少。当然,有很多细节,但您通常可以认为在驱动程序级别上发生的事情 - 无论您的应用程序是否读取/写入任何内容,读取和写入始终由NIC使用DMA执行或不。现在,除此之外,还有一个OS基础架构,允许用户空间应用程序向/从NIC发送和接收数据。当您打开套接字时,操作系统将确定您的应用程序感兴趣的数据类型,并将条目添加到与网络接口通信的应用程序列表中。当发生这种情况时,应用程序开始接收放置在内核中某种应用程序队列中的数据。无论您是否正在调用读取,数据都放在那里。放置数据后,将通知应用程序。内核中的通知机制各不相同,但它们都有类似的想法 - 让应用程序知道可以调用read()数据。一旦数据在“队列”中,应用程序就可以通过调用read()来获取它。阻塞和非阻塞读取之间的区别很简单 - 如果读取阻塞,内核将暂停应用程序的执行,直到数据到达。在非阻塞读取的情况下,控制在任何情况下都返回给应用程序 - 无论是数据还是没有数据。如果后者发生,应用程序可以继续尝试(也就是在套接字上旋转),或等待来自内核的通知说数据可用,然后继续阅读它。现在让我们回到“无所事事”。这意味着套接字被注册只接收一次通知。注册后,应用程序不必执行任何操作,只会收到“数据存在”的通知。那么应用程序应该做的是监听该通知并在数据存在时执行只读。一旦收到足够的数据,应用程序就可以开始以某种方式处理它。了解了所有这些,让我们看看三种方法中哪些更好......

  

在套接字上发布另一个重叠读取,这次是数据包的大小,以便在下次完成时接收它?

这是一个很好的方法。理想情况下,您不必“发布”任何内容,但这取决于操作系统界面的优秀程度。如果您不能“注册”您的应用程序一次,然后在每次有新数据时都继续接收通知,并且在可用时调用read(),那么发布异步读取请求是下一个最好的事情。

  

使用阻塞套接字在例程中读取整个数据包,然后发布另一个与9字节重复的recv?

如果您的应用程序完全没有其他任何操作,并且您只有一个可读取的套接字,这是一个很好的方法。换句话说 - 这是一种简单的方法,非常容易编程,操作系统会完成自身的完成等等。请记住,一旦你有多个套接字可供阅读,你将不得不做一个非常愚蠢的事情,比如每个插槽有一个线程(可怕!),或者使用第一种方法重写你的应用程序。

  

读取块(决定大小)说 - 4096并且有一个计数器来继续读取每个重叠的完成,直到读取数据(比如它将完成12次直到读取所有数据包)。

这是要走的路!实际上,这与方法#1几乎相同,并且优化程度较低,因为尽可能减少到内核的往返次数,并且一次性尽可能多地读取。首先,我想用这些细节纠正第一种方法,但后来我注意到你自己完成了。

希望它有所帮助。祝你好运!

答案 1 :(得分:1)

弗拉德的回答很有意思,但有些操作系统不可知和理论上。这里有一点关注IOCP的设计考虑因素。

您似乎正在从TCP连接中读取消息流,其中消息由一个标题组成,该标题详细说明了完整消息的长度。标头大小固定,为9个字节。

请记住,每个重叠的读取完成将在1个字节和缓冲区大小之间返回,您不应该假设您可以发出9个字节的读取并始终获得完整的标题,您不应该假设您可以随后使用足够大的缓冲区发出读取以完成消息,并在读取完成时完整地接收该消息。您将需要处理返回比预期更少的字节的完成,处理此问题的最佳方法是将WSABUF指针调整到缓冲区的开头,以便后续重叠读取将更多数据读取到位置处的缓冲区中就读完了......

读取数据的最佳方式取决于以下内容:

  • 它的大小(平均而且最大可能的消息大小)
  • 您可能会处理多少联系
  • 您是否可以分段处理消息或仅处理完整消息。
  • 对等方是否可以在没有您回复的情况下发送多条消息,或者它是否为“消息响应”样式协议。

关于如何使用IOCP读取数据的大多数决定都归结为数据复制将发生的位置以及您希望如何方便地生成您正在处理的数据。假设您没有关闭套接字级读缓冲,那么每当您读取数据时都可能会有数据副本。 TCP堆栈将在其每个套接字读取缓冲区中累积数据,并且您的重叠读取将将其复制到您自己的缓冲区中并将其返回给您。

最简单的情况是,如果您可以在邮件到达时分段处理邮件。在这种情况下,只需为您的完整缓冲区大小发出重叠读取并处理完成(缓冲区将包含1个字节和数据的缓冲区大小),发出新的读取(可能在同一缓冲区的末尾),直到您有有足够的数据来处理和处理数据,直到您需要阅读更多内容。这样做的好处是可以发出最小重叠读取次数(对于缓冲区大小),这会将用户模式减少到内核模式转换。

如果您必须将消息作为完整消息处理,那么您如何处理它们取决于它们的大小和缓冲区的大小。您可以发出标头读取(通过指定缓冲区长度仅为9个字节)然后发出更多重叠读取以将完整的消息累积到一个或多个缓冲区中(通过调整缓冲区的起始和长度)和在“每个连接”数据结构中将缓冲区链接在一起。或者,不要为标题发出“特殊”读取,并处理单个读取返回多条消息的可能性。

我有一些示例IOCP服务器可以完成大部分工作,你可以从here下载它们,并在随附的文章中阅读它们。