有啥方法可以使用 IOCP 来通知套接字何时可读/可写?

Posted

技术标签:

【中文标题】有啥方法可以使用 IOCP 来通知套接字何时可读/可写?【英文标题】:Is there any way to use IOCP to notify when a socket is readable / writeable?有什么方法可以使用 IOCP 来通知套接字何时可读/可写? 【发布时间】:2017-01-08 00:01:59 【问题描述】:

我正在寻找某种方法在套接字变为可读/可写时在 I/O 完成端口上获取信号(即下一个发送/接收将立即完成)。基本上我想要WSASelect 的重叠版本。

(是的,我知道对于许多应用程序来说,这是不必要的,您可以继续发出重叠的send 调用。但是在其他应用程序中,您希望延迟生成要发送的消息,直到可能的最后一刻,如所讨论的e.g. here。在这些情况下,这样做很有用 (a) 等待套接字可写,(b) 生成下一条消息,(c) 发送下一条消息。)

到目前为止,我能想出的最好的解决方案是生成一个线程来调用select,然后调用PostQueuedCompletionStatus,这很糟糕而且不是特别可扩展......有没有更好的方法?

【问题讨论】:

你不需要这一切。套接字连接后,他一直“可写”和“可读”。您可以一次有多个重叠发送。但是,有时只有一个 recv 请求存在。您需要在连接后制作 recv,然后在之前的 recv 完成后。直到断开连接。 “将立即完成” - 使用异步 io 时这没有意义 我确实需要这个,并在问题中解释了原因——这是一种为延迟敏感的应用程序最小化发送缓冲的方法。 (或者,我想如果有一种方法可以在总发送缓冲区大小低于某个低水位线时获得警报,那也可以,但我对现有的希望更小......) 发送完成后 - 您会收到通知。因此,例如,当您需要发送大数据时 - 您只能发送块。当这个块的发送完成时——你在 IOCP 中得到了关于这个的通知,在这个通知中——发送另一个块。等等..我已经做了很多次了 Select 在我们使用 IOCP 时根本不需要。只是我们的回调在任何操作完成时调用。 您需要的不仅仅是“可写”您需要使用链接中描述的低水印设置。 @RbMm 如果您关于 TCP 发送缓冲区的声明属实,那么重新传输将是不可能的,并且 TCP 发送缓冲区将是多余的。 【参考方案1】:

事实证明这是可能的!

基本上诀窍是:

使用 WSAIoctl SIO_BASE_HANDLE 查看任何“分层服务提供者” 使用DeviceIoControl 将基本句柄的AFD_POLL 请求提交给AFD 驱动程序(这是select 在内部执行的操作)

有很多很多复杂的情况可能值得理解,但归根结底,上述内容应该在实践中起作用。这应该是一个私有 API,但是 libuv 使用它,而且 MS 的兼容性策略意味着它们永远不会破坏 libuv,所以你很好。详情请阅读此消息开始的线程:https://github.com/python-trio/trio/issues/52#issuecomment-424591743

【讨论】:

【参考方案2】:

为了检测一个套接字是可读的,事实证明有一个未记录但众所周知的民间传说:你可以发出一个“零字节读取”,即一个重叠的WSARecv 与一个零字节接收缓冲区,并且直到有一些数据要读取时才会完成。这有been recommended 用于尝试从大量空闲套接字同时读取的服务器,以避免内存使用问题(显然 IOCP 接收缓冲区被固定到 RAM 中)。在libuv 源代码中可以看到这种技术的一个示例。他们还有一个额外的改进,就是将它与 UDP 套接字一起使用,他们发出一个带有MSG_PEEK 集的零字节接收。 (这很重要,因为如果没有该标志,零字节接收将消耗一个数据包,将其截断为零字节。)MSDN 声称您不能将MSG_PEEK 与重叠 I/O 结合使用,但显然它适用于它们。 ..

当然,这只是答案的一半,因为仍然存在检测可写性的问题。

类似的“零字节发送”技巧可能会奏效吗? (直接用于 TCP,并在 UDP 套接字上添加 MSG_PARTIAL 标志,以避免实际发送零字节数据包。)实验上我已经检查过尝试在不可写的非阻塞上进行零字节发送TCP 套接字返回WSAEWOULDBLOCK,所以这是一个有希望的迹象,但我还没有尝试过重叠 I/O。我最终会解决它并更新这个答案;或者,如果有人想先尝试并发布他们自己的综合答案,那么我可能会接受它:-)

【讨论】:

我认为可写性是 io 大小的函数。我的预感是,只要 tcp-send-buffer-size 减去 combined-size-of-incomplete-sends 高于您的有效负载,WSASend 就会返回待处理。现在,这意味着您必须足够快地处理完成,以便您的启发式算法是最新的,并且您知道发送缓冲区的大小是多少。进入 Nagel 的算法...... 感谢分享,这个技巧很有用!我也很想知道类似的技巧是否适用于检测可写性。 仅供参考,您的答案中的 0 字节读取参考链接不再有效,但它曾经指的是 Microsoft Windows 2nd Ed 的网络编程。第 194 页 @tmm1 感谢您的提醒,我已将其替换为 archive.org 链接 我进行了测试,似乎同样的技巧不适用于可写性。我创建了一个 tcp 连接,将其设置为非阻塞,用(非重叠)WSASends 写入它,直到它返回 EWOULDBLOCK(写入 ~639kb)。然后我尝试了一个与零字节WSABuf 重叠的WSASend:操作返回WSA_IO_PENDING,但是一个完成事件立即到达,lpcbTransfer 设置为0。

以上是关于有啥方法可以使用 IOCP 来通知套接字何时可读/可写?的主要内容,如果未能解决你的问题,请参考以下文章

使用 WSARecv() 和 IOCP 时如何知道套接字何时收到了 FIN 数据包?

Windows IOCP - 单套接字应用程序有啥优势吗?

GLUT:有啥方法可以在事件循环中添加“文件可读”挂钩?

IOCP模型EPOLL模型的比较以及游戏服务器端的一些建议

IOCP模型

如何使用 IOCP 发送文件?