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

Is there any way to use IOCP to notify when a socket is readable / writeable?

我正在寻找某种方法在套接字变为 readable/writeable 时在 I/O 完成端口上获取信号(即下一个 send/recv 将立即完成)。基本上我想要 WSASelect.

的重叠版本

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

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

为了检测套接字是否可读,结果发现有一个未记录但 well-known 的民间传说:您可以发出 "zero byte read",即重叠的 WSARecv使用 zero-byte 接收缓冲区,直到有一些数据要读取时才会完成。这有 been recommended 用于尝试从大量 mostly-idle 套接字同时读取的服务器,以避免内存使用问题(显然 IOCP 接收缓冲区被固定到 RAM 中)。在 libuv 源代码中可以看到此技术的示例。他们还有一个额外的改进,即要将其与 UDP 套接字一起使用,他们会发出一个 zero-byte 接收并设置 MSG_PEEK 。 (这很重要,因为如果没有该标志,zero-byte 接收将消耗一个数据包,将其截断为零字节。)MSDN 声称您不能将 MSG_PEEK 与重叠的 I/O 组合,但是显然对他们有用...

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

类似的 "zero-byte send" 技巧有可能奏效吗? (直接用于 TCP,并在 UDP 套接字上添加 MSG_PARTIAL 标志,以避免实际发送 zero-byte 数据包。)实验上我检查了尝试在 zero-byte 上发送non-writable non-blocking TCP 套接字 returns WSAEWOULDBLOCK,所以这是一个有希望的迹象,但我还没有尝试重叠 I/O。我最终会解决这个问题并更新这个答案;或者,如果有人想先尝试 post 他们自己的综合答案,那么我可能会接受它:-)

事实证明,这是可以的!

基本上诀窍是:

  • 使用 WSAIoctl SIO_BASE_HANDLE 浏览任何 "layered service providers"
  • 使用 DeviceIoControl 向 AFD 驱动程序提交 AFD_POLL 对基本句柄的请求(这是 select 内部所做的)

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