1

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

当完成端口返回 9 个字节时,哪个更好?(性能/良好实践或美学明智)

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

2 回答 2

1

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

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

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

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

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

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

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

希望能帮助到你。祝你好运!

于 2013-02-16T14:41:50.200 回答
1

弗拉德的回答很有趣,但有点操作系统不可知论和理论。这里更侧重于 IOCP 的设计注意事项。

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

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

读取数据的最佳方式取决于以下几点:

  • 它有多大(平均和最大可能的消息大小)
  • 您可能要处理多少个连接
  • 是否可以分段处理消息或仅作为完整消息处理。
  • 对等方是否可以在没有您响应的情况下发送多条消息,或者它是否是“消息-响应”样式协议。

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

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

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

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

于 2013-11-20T11:01:43.673 回答