在IOCP Winsock2客户端中,在连接尝试失败后ConnectEx()超时后,会发生以下情况:“IO完成”排队到相关的IO完成端口。…
不幸的是,看起来没有设置套接字连接超时的内置选项。如何最小化我没有看到这个并基于这个问题 - 如何配置套接字连接超时 - 没人也不看。
一个可能的解决方案将事件句柄传递给I / O请求,如果我们得到的话 ERROR_IO_PENDING - 打电话 RegisterWaitForSingleObject 对于这个事件。如果这个电话会成功 - 我们的 WaitOrTimerCallback 将调用回调函数 - 或者因为I / O将完成(具有任何最终状态)并且此时事件(我们将其传递给I / O请求和 RegisterWaitForSingleObject )将被设置或因为超时( dwMilliseconds )已过期 - 在这种情况下我们需要打电话 CancelIoEx 功能。
ERROR_IO_PENDING
RegisterWaitForSingleObject
WaitOrTimerCallback
CancelIoEx
所以我们说 class IO_IRP : public OVERLAPPED 哪个有引用计数(我们需要保存指针) OVERLAPPED 用于I / O请求传递给它 CancelIoEx 。并且需要确保这一点 OVERLAPPED 仍然没有在另一个新的I / O中使用 - 所以还没有免费)。在这种情况下可能的实施:
class IO_IRP : public OVERLAPPED
OVERLAPPED
class WaitTimeout { IO_IRP* _Irp; HANDLE _hEvent, _WaitHandle, _hObject; static VOID CALLBACK WaitOrTimerCallback( __in WaitTimeout* lpParameter, __in BOOLEAN TimerOrWaitFired ) { UnregisterWaitEx(lpParameter->_WaitHandle, NULL); if (TimerOrWaitFired) { // the lpOverlapped unique here (because we hold reference on it) - not used in any another I/O CancelIoEx(lpParameter->_hObject, lpParameter->_Irp); } delete lpParameter; } ~WaitTimeout() { if (_hEvent) CloseHandle(_hEvent); _Irp->Release(); } WaitTimeout(IO_IRP* Irp, HANDLE hObject) : _hEvent(0), _Irp(Irp), _hObject(hObject) { Irp->AddRef(); } BOOL Create(PHANDLE phEvent) { if (HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL)) { *phEvent = hEvent; _hEvent = hEvent; return TRUE; } return FALSE; } public: static WaitTimeout* Create(PHANDLE phEvent, IO_IRP* Irp, HANDLE hObject) { if (WaitTimeout* p = new WaitTimeout(Irp, hObject)) { if (p->Create(phEvent)) { return p; } delete p; } return NULL; } void Destroy() { delete this; } // can not access object after this call void SetTimeout(ULONG dwMilliseconds) { if (RegisterWaitForSingleObject(&_WaitHandle, _hEvent, (WAITORTIMERCALLBACK)WaitOrTimerCallback, this, dwMilliseconds, WT_EXECUTEONLYONCE|WT_EXECUTEINWAITTHREAD)) { // WaitOrTimerCallback will be called // delete self here return ; } // fail register wait // just cancel i/o and delete self CancelIoEx(_hObject, _Irp); delete this; } };
并使用类似的东西
if (IO_IRP* Irp = new IO_IRP(...)) { WaitTimeout* p = 0; if (dwMilliseconds) { if (!(p = WaitTimeout::Create(&Irp->hEvent, Irp, (HANDLE)socket))) { err = ERROR_NO_SYSTEM_RESOURCES; } } if (err == NOERROR) { DWORD dwBytes; err = ConnectEx(socket, RemoteAddress, RemoteAddressLength, lpSendBuffer, dwSendDataLength, &dwBytes, Irp)) ? NOERROR : WSAGetLastError(); } if (p) { if (err == ERROR_IO_PENDING) { p->SetTimeout(dwMilliseconds); } else { p->Destroy(); } } Irp->CheckErrorCode(err); }
另一个可能的解决方案集定时器 CreateTimerQueueTimer 如果计时器到期 - 请致电 CancellIoEx 或者从这里关闭I / O句柄。与事件解决方案的区别 - 如果I / O将在计时器到期之前完成 - WaitOrTimerCallback 回调函数不会自动调用。如果事件 - I / O子系统在I / O完成时设置事件(在初始挂起状态之后)并且由于(信号状态中的事件)将调用回调。但是在大小写的情况下 - 没有办法将它作为参数传递给io请求(I / O只接受事件句柄)。因此,我们需要通过self保存指向计时器对象的指针,并在I / O完成时手动释放它。所以这里将是2个指向计时器对象的指针 - 一个来自池(由...保存) CreateTimerQueueTimer )和一个来自我们的对象(套接字)类(当I / O完成时我们需要它来取消引用对象)。这需要对包含计时器的对象进行引用计数。从另一方面来说,我们可以使用定时器不用于单个I / O操作,而是用于多个I / O(因为它不直接绑定到某些I / O)
CreateTimerQueueTimer
CancellIoEx