Winsock 接受事件有时会停止发送信号(WSAEventSelect)
我对作为多线程套接字服务器一部分的一段遗留 c++/winsock 代码有疑问。应用程序创建一个线程来处理来自客户端的连接,其中通常同时有数百个连接。它通常可以毫无问题地运行几天(连续),然后突然停止接受连接。这仅发生在生产中,从不测试。
它使用 WSAEventSelect() 来检测 FD_ACCEPT 网络事件。连接处理程序的(简化)代码是:
SOCKET listener;
HANDLE hStopEvent;
// ... initialise listener and hStopEvent, and other stuff ...
HANDLE hAcceptEvent = WSACreateEvent();
WSAEventSelect(listener, hAcceptEvent, FD_ACCEPT);
HANDLE rghEvents[] = { hStopEvent, hAcceptEvent };
bool bExit = false;
while(!bExit)
{
DWORD nEvent = WaitForMultipleObjects(2, rghEvents, FALSE, INFINITE);
switch(nEvent)
{
case WAIT_OBJECT_0:
bExit = true;
break;
case WAIT_OBJECT_1:
HandleConnect();
WSAResetEvent(hAcceptEvent);
break;
case WAIT_ABANDONED_0:
case WAIT_ABANDONED_0 + 1:
case WAIT_FAILED:
LogError();
break;
}
}
从详细的日志记录中我知道,当问题发生时,线程进入 WaitForMultipleObjects() 并且永远不会出现,即使有客户端尝试连接并等待接受。 WAIT_FAILED 和 WAIT_ABANDONED_x 条件永远不会发生。
虽然我没有排除服务器上的配置问题,甚至某种资源泄漏(找不到任何东西),但我也想知道 WSACreateEvent() 创建的事件是否以某种方式与FD_ACCEPT 网络事件 - 导致它永远不会触发。
那么,我在这里做错了什么吗?有什么是我应该做却没有做的吗?或者更好的方法?我将不胜感激任何建议!谢谢。
编辑
套接字是非阻塞套接字。
编辑
使用 kipkennedy 建议的方法解决了问题(如下)。将 hAcceptEvent 更改为自动重置事件,并删除了对不再需要的 WSAResetEvent() 的调用。
I have a problem with a piece of legacy c++/winsock code that is part of a multi-threaded socket server. The application creates a thread that handles connections from clients, of which there are typically a couple of hundred connected at any one time. It typically runs without a problem for several days (continuously), and then suddenly stops accepting connections. This only happens in production, never test.
It uses WSAEventSelect() to detect FD_ACCEPT network events. The (simplified) code for the connection handler is:
SOCKET listener;
HANDLE hStopEvent;
// ... initialise listener and hStopEvent, and other stuff ...
HANDLE hAcceptEvent = WSACreateEvent();
WSAEventSelect(listener, hAcceptEvent, FD_ACCEPT);
HANDLE rghEvents[] = { hStopEvent, hAcceptEvent };
bool bExit = false;
while(!bExit)
{
DWORD nEvent = WaitForMultipleObjects(2, rghEvents, FALSE, INFINITE);
switch(nEvent)
{
case WAIT_OBJECT_0:
bExit = true;
break;
case WAIT_OBJECT_1:
HandleConnect();
WSAResetEvent(hAcceptEvent);
break;
case WAIT_ABANDONED_0:
case WAIT_ABANDONED_0 + 1:
case WAIT_FAILED:
LogError();
break;
}
}
From detailed logging I know that, when the problem occurs, the thread enters WaitForMultipleObjects() and never emerges, even though there are clients attempting to connect and waiting for an accept. The WAIT_FAILED and WAIT_ABANDONED_x conditions never occur.
While I haven't ruled-out a config problem on the server, or even some kind of resource leak (can't find anything), I am also wondering if the event created by WSACreateEvent() is somehow being 'dissassociated' from the FD_ACCEPT network event - causing it to never fire.
So, am I doing something wrong here? Is there something I should be doing that I'm not? Or a better way? I'd appreciate any suggestions! Thanks.
EDIT
The socket is a non-blocking socket.
EDIT
Problem solved by using the approach suggested by kipkennedy (below). Changed hAcceptEvent to be an auto-reset event, and removed the call to WSAResetEvent() which was no-longer needed.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
也许 FD_ACCEPT 在 Accept() 之后、返回和后续 ResetEvent() 之前的 HandleConnect() 期间发出信号。然后,ResetEvent() 最终重置所有信号,并且不会调用重新启用的accept()。例如,可能存在以下顺序:
几个可能的解决方案: 1) 在 HandleConnect() 中的accept() 上循环,直到返回WSAEWOULDBLOCK 2) 使用自动重置事件或在调用 HandleConnect() 之前立即重置事件
Maybe an FD_ACCEPT is signaling during HandleConnect() after the accept() and before the return and subsequent ResetEvent(). Then, ResetEvent() ends up resetting all signals and no re-enabling accept() is ever called. For example, the following sequence is possible:
A couple possible solutions: 1) loop on accept() in HandleConnect() until WSAEWOULDBLOCK is returned 2) use an auto-reset event or immediately reset the event before calling HandleConnect()
代码看起来不错。我唯一可以建议的是调用 WSAWaitForMultipleObjects() 而不是全局版本。
Code looks fine. The only thing I can suggest is calling WSAWaitForMultipleObjects() instead of the global version.
通过阅读文档,似乎< code>WSAEventSelect() 对于通知的处理与
WSAAsyncSelect()
一样简洁。每次有连接进入时,堆栈不会发出FD_ACCEPT
信号。对于 Winsock 来说,通知是它的表达方式:解决方案是在调用
WSAEventSelect()
之前调用accept()
,并且仅在获取WSAEWOULDBLOCK
后调用WSAEventSelect()
代码>.为了使其按预期工作,您需要将侦听套接字设置为非阻塞。 (这似乎是显而易见的,但实际上并不是必需的。)From reading the docs, it appears that
WSAEventSelect()
is as parsimonious about notifications asWSAAsyncSelect()
. The stack doesn't signalFD_ACCEPT
every time a connection comes in. To Winsock, the notification is its way of saying:The solution is to call
accept()
before callingWSAEventSelect()
, and only callWSAEventSelect()
after you getWSAEWOULDBLOCK
. For this to work as you expect, you need to have set the listening socket to non-blocking. (That might seem obvious, but it isn't actually required.)发生接受事件后,您不得执行 WSAResetEvent(hAcceptEven)。您必须发出 WSAEnumNetworkEvents(listener、hAcceptEvent、&some_struct)。此函数清除套接字的内部状态(ar 将此状态复制到 some_struct 中),之后您可以接收新连接。
After an accept event occured you must not do a WSAResetEvent(hAcceptEven). You must issue a WSAEnumNetworkEvents ( listener, hAcceptEvent, &some_struct). This functions clears the internal state of the socket (ar copies this state into some_struct) and after that you can receive new connections.