我正在使用 BindIoCompletionCallback 构建一个 Visual C++ WinSock TCP 服务器,它可以正常接收和发送数据,但我找不到检测超时的好方法:SetSockOpt/SO_RCVTIMEO/SO_SNDTIMEO 对非阻塞套接字没有影响,如果对等方不是发送任何数据时,根本不会调用 CompletionRoutine。
我正在考虑将 RegisterWaitForSingleObject 与 OVERLAPPED 的 hEvent 字段一起使用,这可能会起作用,但是根本不需要 CompletionRoutine,我还在使用 IOCP 吗?如果我只使用 RegisterWaitForSingleObject 而不使用 BindIoCompletionCallback 是否存在性能问题?
更新:代码示例:
我的第一次尝试:
bool CServer::Startup() {
SOCKET ServerSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
WSAEVENT ServerEvent = WSACreateEvent();
WSAEventSelect(ServerSocket, ServerEvent, FD_ACCEPT);
......
bind(ServerSocket......);
listen(ServerSocket......);
_beginthread(ListeningThread, 128 * 1024, (void*) this);
......
......
}
void __cdecl CServer::ListeningThread( void* param ) // static
{
CServer* server = (CServer*) param;
while (true) {
if (WSAWaitForMultipleEvents(1, &server->ServerEvent, FALSE, 100, FALSE) == WSA_WAIT_EVENT_0) {
WSANETWORKEVENTS events = {};
if (WSAEnumNetworkEvents(server->ServerSocket, server->ServerEvent, &events) != SOCKET_ERROR) {
if ((events.lNetworkEvents & FD_ACCEPT) && (events.iErrorCode[FD_ACCEPT_BIT] == 0)) {
SOCKET socket = accept(server->ServerSocket, NULL, NULL);
if (socket != SOCKET_ERROR) {
BindIoCompletionCallback((HANDLE) socket, CompletionRoutine, 0);
......
}
}
}
}
}
}
VOID CALLBACK CServer::CompletionRoutine( __in DWORD dwErrorCode, __in DWORD dwNumberOfBytesTransfered, __in LPOVERLAPPED lpOverlapped ) // static
{
......
BOOL res = GetOverlappedResult(......, TRUE);
......
}
class CIoOperation {
public:
OVERLAPPED Overlapped;
......
......
};
bool CServer::Receive(SOCKET socket, PBYTE buffer, DWORD length, void* context)
{
if (connection != NULL) {
CIoOperation* io = new CIoOperation();
WSABUF buf = {length, (PCHAR) buffer};
DWORD flags = 0;
if ((WSARecv(Socket, &buf, 1, NULL, &flags, &io->Overlapped, NULL) != 0) && (GetLastError() != WSA_IO_PENDING)) {
delete io;
return false;
} else return true;
}
return false;
}
正如我所说,如果客户端实际上正在向我发送数据,它工作正常,“接收”没有阻塞,调用 CompletionRoutine,接收到数据,但这里有一个问题,如果客户端没有向我发送任何数据,怎么能超时后我放弃了?
由于 SetSockOpt/SO_RCVTIMEO/SO_SNDTIMEO 在这里无济于事,我想我应该使用 OVERLAPPED 结构中的 hEvent 字段,该字段将在 IO 完成时发出信号,但是 WaitForSingleObject / WSAWaitForMultipleEvents 将阻止接收调用,我希望接收总是立即返回,所以我使用了 RegisterWaitForSingleObject 和 WAITORTIMERCALLBACK。它起作用了,在超时后调用回调,或者 IO 完成,但现在我有两个回调用于任何单个 IO 操作,CompletionRoutine 和 WaitOrTimerCallback:
如果 IO 完成,它们将被同时调用,如果 IO 未完成,WaitOrTimerCallback 将被调用,然后我调用 CancelIoEx,这导致 CompletionRoutine 被调用并出现一些 ABORTED 错误,但这是一个竞争条件,可能是 IO将在我取消它之前完成,然后...... blahblah,总而言之相当复杂。
然后我意识到我实际上根本不需要 BindIoCompletionCallback 和 CompletionRoutine,并且从 WaitOrTimerCallback 做所有事情,它可能会工作,但这是一个有趣的问题,我想首先构建一个基于 IOCP 的 Winsock 服务器,并认为 BindIoCompletionCallback 是最简单的方法是使用 Windows 本身提供的线程池,现在我最终得到一个没有 IOCP 代码的服务器?还是 IOCP 吗?还是我应该忘记 BindIoCompletionCallback 并构建自己的 IOCP 线程池实现?为什么 ?