我遇到了一个遗留的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()的调用。
答案 0 :(得分:4)
在accept()之后以及返回和随后的ResetEvent()之前,可能在HandleConnect()期间发出FD_ACCEPT信号。然后,ResetEvent()最终重置所有信号,并且不会调用重新启用的accept()。例如,以下序列是可能的:
一些可能的解决方案:1)在HandleConnect()中的accept()循环,直到返回WSAEWOULDBLOCK 2)使用自动重置事件或在调用HandleConnect()之前立即重置事件
答案 1 :(得分:1)
代码看起来很好。我唯一可以建议的是调用WSAWaitForMultipleObjects()而不是全局版本。
答案 2 :(得分:1)
从阅读the docs开始,WSAEventSelect()
似乎与WSAAsyncSelect()
的通知一样简洁。每次连接进入时,堆栈都不会发出FD_ACCEPT
信号。对于Winsock,通知是这样说的:
您之前致电
accept()
,但WSAEWOULDBLOCK
失败了。继续再打电话,这次应该会成功。
解决方法是在致电accept()
之前致电WSAEventSelect()
,然后在获得WSAEventSelect()
后致电WSAEWOULDBLOCK
。为了使其按预期工作,您需要将侦听套接字设置为非阻塞。 (这看起来很明显,但实际上并不需要。)
答案 3 :(得分:1)
发生接受事件后,您不能执行WSAResetEvent(hAcceptEven)。您必须发出WSAEnumNetworkEvents(listener,hAcceptEvent和& some_struct)。此函数清除套接字的内部状态(将此状态复制到some_struct中),之后您可以接收新连接。