IOCP-张贴重叠或读取的数据包

IOCP - post overlapped or read packet?

本文关键字:数据包 读取 张贴 重叠 IOCP-      更新时间:2023-10-16

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

当完成端口返回9个字节时,该怎么办?(性能/良好实践或美学)

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

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

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

这是一个很好的方法。理想情况下,你不必"发布"任何内容,但这取决于操作系统界面的好坏。如果你不能"注册"你的应用程序一次,然后每次有新数据可用时都会收到通知,并在有新数据时调用read(),那么发布异步读取请求是下一个最好的选择。

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

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

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

这就是要走的路!事实上,这与方法#1几乎相同,方法#1进行了很好的优化,以尽可能少地往返于内核,并一次读取尽可能多的内容。首先,我想用这些细节来纠正第一种方法,但后来我注意到你自己做了。

希望能有所帮助。祝你好运

Vlad的回答很有趣,但有些操作系统不可知论和理论性。以下内容更侧重于IOCP的设计注意事项。

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

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

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

  • 它有多大(平均值和最大可能的消息大小)
  • 你可能要处理多少关系
  • 无论您是可以分段处理消息,还是仅作为完整消息处理消息
  • 对等方是否可以在没有您响应的情况下发送多条消息,或者是否是"消息响应"式协议

关于如何使用IOCP读取数据的大多数决策都取决于数据复制将在哪里进行,以及您希望如何方便地制作正在处理的数据。假设您没有关闭套接字级读取缓冲,那么无论何时读取数据,都可能存在数据副本。TCP堆栈将在其每个套接字的读取缓冲区中累积数据,您的重叠读取将把数据复制到您自己的缓冲区中并返回给您。

最简单的情况是,如果您可以在消息到达时对其进行分段处理。在这种情况下,只需对整个缓冲区大小发出重叠读取并处理完成(缓冲区将包含1个字节和缓冲区大小之间的数据),发出新读取(可能到同一缓冲区的末尾),直到您有足够的数据要处理,然后处理数据,直到您需要读取更多。这样做的好处是,您可以发出最小数量的重叠读取(针对您的缓冲区大小),从而减少用户模式到内核模式的转换。

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

我有一个IOCP服务器的例子,它做了大部分这些事情,你可以从这里下载它们,并在随附的文章中阅读它们。