在什么情况下(如果有的话),在WSAEventSelect表示准备就绪后,ReadFile可以挂起



这太难看了。我们遇到了一些由安全扫描软件引起的奇怪的挂断。我怀疑它有一个非标准的TCP/IP堆栈,客户问为什么会挂断。

静态分析表明,只有两个可能的挂断位置。挂断必须在套接字上的ReadFile()WriteFile()中;并且除非扫描器被设计为通过将窗口大小设置为零来使CCD_ 4挂起,否则CCD_。如果WriteFile()真的回来了,即使它没有取得进展,我也能把它从楔入状态中敲出来。我也认为日志状态与返回的WriteFile()不一致。

因此,在ReadFile()上:这是调用序列:

SOCKET conn;
HANDLE unwedgeEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
HANDLE listenEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (listenEvent == NULL) return;
//...
conn = accept(lstn);
for (;;) {
HANDLE wakeup[2];
wakeup[0] = unwedgeEvent;
wakeup[1] = listenEvent;
if (WSAEventSelect(conn, socket, FD_READ | FD_CLOSE) == 0) {
// Log error 
break;
}
which = WaitForMultipleObjects(2, wakeup, FALSE, INFINITE);
if (which < 0) {
// Log error
break;
}
if (which == 1) {
DWORD read;
r = ReadFile(conn, chunk, 4096, &read, NULL);
if (r == 0) {
// Handle error -- not stuck here
} else {
// Handle data -- not stuck here either
}
}
if (which == 0) break;
}

其中,发信号的unwedgeEvent无法完成任何任务,线程永远处于停滞状态。

所以真正的问题是我疯了吗?还是这真的是一件可能发生的事情?

因此,这在某种程度上偏离了深层次;我根本不需要非阻塞插座。我需要一个select(),它为套接字和非套接字的事物提供句柄参数。

以下API序列未挂在ReadFile:中

---------------------------------------------------------------
Sender                                 B Receiver
WSAEventSelect
* WaitForMultipeObjects
send(buffer size = 1)
ReadFile(size = 1)
WSAEventSelect
* WaitForMultipeObjects
send(buffer size = 1)
ReadFile(size = 1)
................................................................
WSAEventSelect
* WaitForMultipeObjects
send(buffer size = 2)
ReadFile(size = 1)
WaitForMultipeObjects
ReadFile(size = 1)
................................................................
WSAEventSelect
* WaitForMultipeObjects
send(buffer size = 1)
send(buffer size = 1)
ReadFile(size = 1)
WaitForMultipeObjects
ReadFile(size = 1)
................................................................
WSAEventSelect
send(buffer size = 1)
WaitForMultipeObjects
send(buffer size = 1)
ReadFile(size = 1)
WaitForMultipeObjects
ReadFile(size = 1)
................................................................
WSAEventSelect
send(buffer size = 1)
WaitForMultipeObjects
send(buffer size = 1)
ReadFile(size = 2)
* WaitForMultipeObjects
send(buffer size = 1)
ReadFile(size = 1)
................................................................
WSAEventSelect
send(buffer size = 1)
WaitForMultipeObjects
ReadFile(size = 1)
send(buffer size = 1)
WaitForMultipeObjects
ReadFile(size = 1)

我看到您的代码有很多问题:

  • 您应该使用WSACreateEvent()WSAWaitForMultipleEvents(),而不是CreateEvent()WaitForMultipleObjects()。尽管当前的实现是前一个API简单地映射到后一个API,但Microsoft可以随时更改该实现,而不会破坏正确使用前一个api的代码。

  • 在呼叫WSAEventSelect()时,socket应改为listenEvent

  • WSAEventSelect()在失败时返回SOCKET_ERROR(-1(,而不是像您编码的那样返回0。

  • 您根本没有调用WSAEnumNetworkEvents(),需要这样做才能确定FD_READ是否是实际触发的事件类型,并清除套接字的事件状态并重置事件对象。因此,您可能正在处理一个过时的读取状态,这可以解释为什么在实际上没有可用读取的情况下最终调用ReadFile()

  • WSAEventSelect()将套接字置于非阻塞模式(根据其文档(,因此ReadFile()(或任何其他读取函数(实际上不可能阻塞套接字。但是,它可能会立即失败,并出现WSAEWOULDBLOCK错误,因此请确保您没有将该情况视为致命错误。

  • WSAEventSelect()文档没有将ReadFile()列为调用WSAEnumNetworkEvents()时重新启用事件的支持函数。尽管Socket Handles文档确实指出WinsockSOCKET可以ReadFile()等非Winsock I/O函数一起使用,但建议WriteFile()0只能与Winsock函数一起使用。因此,应该使用send()/recv()WSASend()/WSARecv(),而不是WriteFile()/ReadFile()

话虽如此,可以尝试更像以下的方法:

HANDLE unwedgeEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (unwedgeEvent == NULL) {
// Log error 
return;
}
...
SOCKET conn = accept(lstn, NULL, NULL);
if (conn == INVALID_SOCKET) {
// Log error 
return;
}
...
WSAEVENT listenEvent = WSACreateEvent();
if (listenEvent == NULL) {
// Log error 
}
else if (WSAEventSelect(conn, listenEvent, FD_READ | FD_CLOSE) == SOCKET_ERROR) {
// Log error 
}
else {
WSAEVENT wakeup[2];
wakeup[0] = (WSAEVENT) unwedgeEvent;
wakeup[1] = listenEvent;
char chunk[4096];               
int read;
do {
DWORD which = WSAWaitForMultipleEvents(2, wakeup, FALSE, WSA_INFINITE, FALSE);
if (which == WSA_WAIT_FAILED) {
// Log error
break;
}
if (which == WSA_WAIT_EVENT_0) {
break;
}
if (which == (WSA_WAIT_EVENT_0+1)) {
WSANETWORKEVENTS events = {};
if (WSAEnumNetworkEvents(conn, listenEvent, &events) == SOCKET_ERROR) {
// Log error
break;
}
if (events.lNetworkEvents & FD_READ) {
read = recv(conn, chunk, sizeof(chunk), 0);
if (read == SOCKET_ERROR) {
if (WSAGetLastError() != WSAEWOULDBLOCK) {
// Log error
break;
}
}
else if (read == 0) {
break;
}
else {
// Handle chunk up to read number of bytes
}
}
if (events.lNetworkEvents & FD_CLOSE) {
break;
}
}
}
while (true);
WSACloseEvent(listenEvent);
}
closesocket(conn);

相关内容

最新更新