这太难看了。我们遇到了一些由安全扫描软件引起的奇怪的挂断。我怀疑它有一个非标准的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);