不幸的是,似乎没有设置套接字连接超时的内置选项。基于这个问题,我看不到这一点有多少 - How to configure socket connect timeout - 没有人也看不到。
一种可能的解决方案将事件句柄传递给 I/O 请求,如果我们得到 ERROR_IO_PENDING - 请为此事件调用 RegisterWaitForSingleObject。如果此调用成功 - 我们的 WaitOrTimerCallback 回调函数将被调用 - 或者因为 I/O 将完成(具有任何最终状态)并且此时事件(我们将其传递给 I/O 请求和 RegisterWaitForSingleObject ) 将被设置或因为超时 (dwMilliseconds) 已过期 - 在这种情况下,我们需要调用 CancelIoEx 函数。
假设我们有 class IO_IRP : public OVERLAPPED,它有引用计数(我们需要保存指向 OVERLAPPED 的指针,用于 I/O 请求以将其传递给 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 设置计时器,如果计时器到期 - 调用CancellIoEx 或从此处关闭 I/O 句柄。与事件解决方案的区别 - 如果 I/O 将在计时器到期之前完成 - 将不会自动调用 WaitOrTimerCallback 回调函数。万一事件 - 当 I/O 完成时(在初始挂起状态之后),I/O 子系统设置事件,并且由于该事件(信号状态中的事件),将调用回调。但万一计时器 - 无法将其作为参数传递给 io 请求(I/O 仅接受事件句柄)。因此,我们需要自己保存指向计时器对象的指针,并在 I/O 完成时手动释放它。所以这里将有 2 个指向计时器对象的指针 - 一个来自池(由 CreateTimerQueueTimer 保存)和一个来自我们的对象(套接字)类(当 I/O 完成时我们需要它来取消引用对象)。这也需要对包含计时器的对象进行引用计数。另一方面,我们可以使用定时器,而不是单个 I/O 操作,而是多个 I/O(因为它不直接绑定到某些 I/O)