使用MsgWaitForMultipleObjects如何可靠地检测断开连接的TCP套接字?

6
Twisted包括一个基于MsgWaitForMultipleObjects实现的反应器。 显然,反应器在可靠地注意到TCP连接结束时存在问题,至少在对等方发送一些字节然后快速关闭连接的情况下是如此。 看起来发生的是:
  1. 反应堆使用一些套接字句柄和 QS_ALLINPUT 调用 MsgWaitForMultipleObjects。
  2. 调用完成并指示处于此状态的套接字句柄(即等待读取且已被对等方关闭)处于活动状态。
  3. 反应堆将此通知分派给常见的 TCP 实现。
  4. TCP 实现从套接字中读取可用字节。有一些字节,它们被传递到应用程序代码。
  5. 控制权返回到反应堆,最终再次调用 MsgWaitForMultipleObjects。
  6. MsgWaitForMultipleObjects 永远不会再次指示该句柄处于活动状态。TCP 实现永远无法再次查看套接字,因此无法检测到连接已关闭。
这使得 MsgWaitForMultipleObjects 看起来像是一种边缘触发通知机制。MSDN文档中说:
Waits until one or all of the specified objects are in the signaled state
or the time-out interval elapses.

这听起来不像是边缘触发,而更像是电平触发。

MsgWaitForMultipleObjects 真的是边缘触发吗?还是它是电平触发,而这种错误行为是由其行为的某些其他方面引起的?

附录 WSAEventSelect 的 MSDN 文档 更详细地解释了这里正在发生的事情,包括指出 FD_CLOSE 基本上是一次性事件。一旦它被触发,你就再也不会收到它了。这在一定程度上解释了为什么 Twisted 会有这个问题。尽管如此,我仍然很想知道如何有效地使用 MsgWaitForMultipleObjects 来克服这个限制。


1
我实在是无法理解这段代码,但是它似乎没有调用WSAEnumNetworkEvents()来确定哪些网络事件实际上发生了。我认为你是对的,FD_CLOSE只会触发一次,所以你需要在某个地方设置一个标志来让自己知道连接已经关闭了。 - Luke
1个回答

1
为了使用WSAEventSelect并区分活动,您需要调用WSAEnumNetworkEvents。确保处理报告的每个事件,而不仅仅是第一个。 WSAAsyncSelect使确定原因变得容易,并经常与MsgWaitForMultipleObjects一起使用。
因此,您可以使用WSAAsyncSelect代替WSAEventSelect
此外,我认为您对边缘触发和电平触发之间的区别存在基本误解。您的推理似乎更与自动重置与手动重置事件有关。

1
谢谢。WSAAsyncSelect看起来很有趣。根据它的文档,似乎不需要多个事件,因为"FD_CLOSE直到所有挂起的数据都被读取完才会被传递"。你认为这个保证足够使一个事件的方法可行吗?此外,关于边缘触发和水平触发,我在写这些注释之前并不知道重置是某些事件发生的事情;考虑到重置,边缘和水平术语似乎根本不适用。你同意这个观点吗? - Jean-Paul Calderone
@Jean-PaulCalderone:下一句话是“应用程序应该在收到FD_CLOSE时检查剩余数据,以避免丢失任何数据的可能性。”,这似乎与前面的句子相矛盾。但听起来像你的问题不是确定是否在关闭后读取数据,而是区分“套接字关闭”和“缓冲区中有数据”之间的区别。 - Ben Voigt
你只能通过WSAEventSelect将单个事件对象与套接字关联,但该事件对象可以通知您多个(类型的)网络事件。通常,您只关心FD_CONNECT / FD_ACCEPT,FD_READ和FD_CLOSE。 - Luke
@Luke:但是你如何找到哪个网络事件设置了内核事件呢?啊,WSAEnumNetworkEvents就是缺失的一块。 - Ben Voigt
WSAEventSelect/WSAEnumNetworkEvents 的组合似乎是个赢家。由于我没有窗口,WSAAsyncSelect 最终变得不是很有用。谢谢! - Jean-Paul Calderone

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接