Тайм-аут для ConnectEx () в режиме IOCP? - PullRequest
0 голосов
/ 03 марта 2019

В клиенте IOCP Winsock2 после ConnectEx() тайм-аута при неудачной попытке подключения происходит следующее:

  1. «Завершение ввода-вывода» ставится в очередь к соответствующему завершению ввода-выводаПорт.

  2. GetQueuedCompletionStatus() возвращает FALSE.

  3. WSAGetOverlappedResult() возвращает WSAETIMEDOUT.

Что определяет период ожидания между звонками ConnectEx() и 1 выше?Как я могу сократить этот период ожидания?

Я знаю, что можно ждать ConnectEx(), передав ему заполненную структуру OVERLAPPED.hEvent = WSACreateEvent(), а затем ждать этого события, например, с WaitForSingleObject(Overlapped.hEvent, millisec) дотайм-аут после того, как не было установлено соединение в течение millisec периода времени.НО, это решение выходит за рамки этого вопроса, потому что оно не относится к модели уведомлений IOCP.

1 Ответ

0 голосов
/ 06 марта 2019

, к сожалению, не похоже на встроенную опцию для установки времени ожидания подключения к сокету.как минимум, я не вижу это и на основе этого вопроса - Как настроить время ожидания подключения сокета - никто не просматривает тоже.

один из возможных решений передать дескриптор события в запрос ввода-вывода, и если мыполучил ERROR_IO_PENDING - позвоните RegisterWaitForSingleObject для этого события.если этот вызов будет успешным - будет вызвана наша функция обратного вызова WaitOrTimerCallback - или потому что ввод / вывод будет завершен (с любым окончательным статусом) и в этот момент событие (которое мы передаем как I /Запрос O и RegisterWaitForSingleObject) будут установлены или из-за истечения времени ожидания ( dwMilliseconds ) - в этом случае нам нужно вызвать функцию CancelIoEx.

так скажем, у нас есть class IO_IRP : public OVERLAPPED с подсчетом ссылок (нам нужно сохранить указатель на OVERLAPPED, используемый в запросе ввода / вывода для передачи его в CancelIoEx. И нужноуверен, что это 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 или закройте ручку ввода / вывода отсюда.разница с решением события - если ввод / вывод будет завершен до истечения таймера - функция обратного вызова WaitOrTimerCallback не будет вызываться автоматически.в случае события - устанавливается событие подсистемы ввода / вывода, когда завершается ввод / вывод (после первоначального состояния ожидания) и благодаря этому (событие в состоянии сигнала) будет вызываться обратный вызов.но в случае таймера - ни в коем случае не передавать его в запрос io как параметр (I / O принимает только дескриптор события).В результате нам нужно сохранить указатель на объект таймера самостоятельно и вручную освободить его, когда завершится ввод-вывод.так что здесь будет 2 указателя на объект таймера - один из пула (сохраненный CreateTimerQueueTimer) и один из нашего класса объекта (сокета) (он нам нужен для объекта разыменования после завершения ввода / вывода).это требует подсчета ссылок на объект, который тоже инкапсулирует таймер.с другой стороны, мы можем использовать таймер не для одной операции ввода-вывода, а для нескольких операций ввода-вывода (поскольку это не прямое связывание с некоторыми операциями ввода-вывода)

...