IOCP模式下的ConnectEx()超时?

时间:2019-03-03 13:48:06

标签: windows sockets winapi winsock2

在IOCP Winsock2客户端中,如果ConnectEx()尝试连接失败而超时,则会发生以下情况:

  1. “ IO完成”排队到关联的IO完成端口。

  2. GetQueuedCompletionStatus()返回FALSE。

  3. WSAGetOverlappedResult()返回WSAETIMEDOUT

由什么决定调用ConnectEx()和以上1之间的超时时间?如何缩短此超时时间?

我知道可以通过向其传递填充结构ConnectEx()来等待OVERLAPPED.hEvent = WSACreateEvent(),然后等待此事件,例如WaitForSingleObject(Overlapped.hEvent, millisec)时间段内未建立连接后,millisec会超时。但是,该解决方案不在此问题的范围内,因为它没有引用IOCP通知模型。

1 个答案:

答案 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)