IOCP 模式下 ConnectEx() 超时?
Timeout for ConnectEx() in IOCP mode?
在 IOCP Winsock2 客户端中,ConnectEx()
连接尝试超时后,会发生以下情况:
"IO completion" 排队到关联的 IO 完成端口。
GetQueuedCompletionStatus()
returns 错误。
WSAGetOverlappedResult()
returns WSAETIMEDOUT
.
什么决定了调用ConnectEx()
和上面的1之间的超时时间?我怎样才能缩短这个超时时间?
我知道可以等待 ConnectEx()
传递给它一个完整的结构 OVERLAPPED.hEvent = WSACreateEvent()
然后等待这个事件,例如WaitForSingleObject(Overlapped.hEvent, millisec)
在 millisec
时间段内未建立连接后超时。但是,该解决方案超出了这个问题的范围,因为它没有引用 IOCP 通知模型。
不幸的是,似乎没有用于设置套接字连接超时的内置选项。最低限度我不看这个和基于这个问题 - How to configure socket connect timeout - 没有人不看。
一个可能的解决方案将事件句柄传递给 I/O 请求,如果我们得到 ERROR_IO_PENDING
- 调用 RegisterWaitForSingleObject
for this event. if this call will be successful - our WaitOrTimerCallback
callback function will be called - or because I/O will be complete (with any final status) and at this moment event (which we pass both to I/O request and RegisterWaitForSingleObject
) will be set or because timeout (dwMilliseconds) expired - in this case we need call CancelIoEx
函数。
所以假设我们有 class IO_IRP : public OVERLAPPED
有引用计数(我们需要保存指向 I/O 请求中使用的 OVERLAPPED
的指针以将其传递给 CancelIoEx
。并且需要确保这个 OVERLAPPED
还没有在另一个新的 I/O 中使用——所以还不是免费的)。在这种情况下可能的实施方式:
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
and if timer expired - call CancellIoEx
or close I/O handle from here. difference with event solution - if I/O will be completed before timer expired - the WaitOrTimerCallback
callback function will be not automatically called. in case event - I/O subsystem set event when I/O complete (after intial pending status) and thanks to that (event in signal state) callback will be called. but in case timer - no way pass it to io request as parameter (I/O accept only event handle). as result we need save pointer to timer object by self and manually free it when I/O complete. so here will be 2 pointer to timer object - one from pool (saved by CreateTimerQueueTimer
设置计时器)和一个来自我们的对象(套接字)class(当 I/O 完成时我们需要它来取消引用对象)。这也需要对封装计时器的对象进行引用计数。从另一方面来说,我们可以使用计时器,而不是用于单个 I/O 操作,而是用于多个 I/O (因为它不直接绑定到某些 I/O)
在 IOCP Winsock2 客户端中,ConnectEx()
连接尝试超时后,会发生以下情况:
"IO completion" 排队到关联的 IO 完成端口。
GetQueuedCompletionStatus()
returns 错误。WSAGetOverlappedResult()
returnsWSAETIMEDOUT
.
什么决定了调用ConnectEx()
和上面的1之间的超时时间?我怎样才能缩短这个超时时间?
我知道可以等待 ConnectEx()
传递给它一个完整的结构 OVERLAPPED.hEvent = WSACreateEvent()
然后等待这个事件,例如WaitForSingleObject(Overlapped.hEvent, millisec)
在 millisec
时间段内未建立连接后超时。但是,该解决方案超出了这个问题的范围,因为它没有引用 IOCP 通知模型。
不幸的是,似乎没有用于设置套接字连接超时的内置选项。最低限度我不看这个和基于这个问题 - How to configure socket connect timeout - 没有人不看。
一个可能的解决方案将事件句柄传递给 I/O 请求,如果我们得到 ERROR_IO_PENDING
- 调用 RegisterWaitForSingleObject
for this event. if this call will be successful - our WaitOrTimerCallback
callback function will be called - or because I/O will be complete (with any final status) and at this moment event (which we pass both to I/O request and RegisterWaitForSingleObject
) will be set or because timeout (dwMilliseconds) expired - in this case we need call CancelIoEx
函数。
所以假设我们有 class IO_IRP : public OVERLAPPED
有引用计数(我们需要保存指向 I/O 请求中使用的 OVERLAPPED
的指针以将其传递给 CancelIoEx
。并且需要确保这个 OVERLAPPED
还没有在另一个新的 I/O 中使用——所以还不是免费的)。在这种情况下可能的实施方式:
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
and if timer expired - call CancellIoEx
or close I/O handle from here. difference with event solution - if I/O will be completed before timer expired - the WaitOrTimerCallback
callback function will be not automatically called. in case event - I/O subsystem set event when I/O complete (after intial pending status) and thanks to that (event in signal state) callback will be called. but in case timer - no way pass it to io request as parameter (I/O accept only event handle). as result we need save pointer to timer object by self and manually free it when I/O complete. so here will be 2 pointer to timer object - one from pool (saved by CreateTimerQueueTimer
设置计时器)和一个来自我们的对象(套接字)class(当 I/O 完成时我们需要它来取消引用对象)。这也需要对封装计时器的对象进行引用计数。从另一方面来说,我们可以使用计时器,而不是用于单个 I/O 操作,而是用于多个 I/O (因为它不直接绑定到某些 I/O)