在IOCP Winsock2客户端中,如果ConnectEx()
尝试连接失败而超时,则会发生以下情况:
“ IO完成”排队到关联的IO完成端口。
GetQueuedCompletionStatus()
返回FALSE。
WSAGetOverlappedResult()
返回WSAETIMEDOUT
。
由什么决定调用ConnectEx()
和以上1之间的超时时间?如何缩短此超时时间?
我知道可以通过向其传递填充结构ConnectEx()
来等待OVERLAPPED.hEvent = WSACreateEvent()
,然后等待此事件,例如WaitForSingleObject(Overlapped.hEvent, millisec)
时间段内未建立连接后,millisec
会超时。但是,该解决方案不在此问题的范围内,因为它没有引用IOCP通知模型。
答案 0 :(得分:1)
看起来好像没有用于设置套接字连接超时的内置选项。我最少没有看这个,并且基于这个问题-How to configure socket connect timeout-没有人也没有看。
一个可能的解决方案将事件句柄传递给I / O请求,如果我们得到ERROR_IO_PENDING
-为此事件调用RegisterWaitForSingleObject
。如果此调用成功-我们的WaitOrTimerCallback
回调函数将被调用-或因为I / O将完成(具有任何最终状态),并且此时此事件(我们将其传递给I / O请求和{ {3}}将会被设置,或者因为超时( dwMilliseconds )已过期-在这种情况下,我们需要调用RegisterWaitForSingleObject
函数。
因此,假设我们有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);
}
另一个可能的解决方案是通过CancelIoEx
设置计时器,如果计时器到期,请从此处调用CancellIoEx
或关闭I / O句柄。与事件解决方案的区别-如果I / O将在计时器到期之前完成-CreateTimerQueueTimer
回调函数将不会自动调用。万一事件-当I / O完成时(在初始暂挂状态之后)并且由于该事件(信号状态中的事件),将调用I / O子系统设置事件。但如果使用计时器,则无法将其作为参数传递给io请求(I / O仅接受事件句柄)。结果,我们需要自行保存指向计时器对象的指针,并在I / O完成后手动释放它。因此这里有2个指向计时器对象的指针-一个来自池(由WaitOrTimerCallback
保存)和一个来自我们的对象(套接字)类的指针(I / O完成后,我们需要使用它作为取消引用的对象)。这也需要引用封装计时器的对象。另一方面,我们可以将计时器用于单个I / O操作,而不是用于多个I / O(因为它不能直接绑定到某些I / O)