Сокеты с использованием GetQueuedCompletionStatus и ERROR_MORE_DATA

Я пытаюсь использовать GetQueuedCompletionStatus с winsocks, но я не могу понять, как это правильно. Процедура выглядит следующим образом:

void foo() {
    ...
    SOCKET sck = WSASocket(AF_INET, SOCK_DGRAM, 0, 
        NULL, 0, WSA_FLAG_OVERLAPPED);
    ....
    bind(sck,(struct sockaddr *)&addr,sizeof(struct sockaddr_in));
    HANDLE hPort = CreateIoCompletionPort((HANDLE)sck, NULL, 0, 0 );
    OVERLAPPED pOverlapped = {0,};
    WSARecvFrom(sck,NULL,0,NULL,NULL,(struct sockaddr *)&laddr,&lsize,&pOverlapped,0);
    BOOL bReturn = GetQueuedCompletionStatus(
            hPort,
            &rbytes,
            (LPDWORD)&lpContext,
            &pOutOverlapped,
            INFINITE);
    ...
}

Затем я отправляю некоторые сетевые данные на связанный порт из внешнего инструмента. GetQueuedCompletionStatus возвращает FALSE, а GetLastError() возвращает ERROR_MORE_DATA, что звучит правильно, так как я не предоставил буфер в WSARecvFrom.

Вопрос в том, как я могу предоставить буфер для фактического получения данных из неудачной операции ввода-вывода?

Я попытался выпустить WSARecvFrom с исходной перекрытой структурой, но он просто ставит в очередь очередное чтение, и последующий вызов GetQueuedCompletionStatus не возвращает, пока не будет отправлено больше сетевых данных.

Вызов WSARecvFrom без перекрывающейся структуры блокирует его, а также не возвращает до тех пор, пока не будет отправлено больше сетевых данных.

Итак, как я могу правильно обработать ERROR_MORE_DATA, не теряя данные из первой операции?

1 ответ

Решение

Вы должны предоставить буфер для WSARecvFrom()так же, как с любой операцией чтения, независимо от того, используете ли вы IOCP или нет. Вы должны убедиться, что буфер остается действительным в памяти до завершения операции IOCP. IOCP заполняет предоставленный вами буфер и затем уведомляет порт завершения о завершении.

UDP не может передавать более 65535 байтов в одной дейтаграмме, поэтому вы можете использовать это как максимальный размер буфера.

В вашем примере ваш код написан для синхронной работы (без цели использования IOCP), поэтому вы можете использовать локальный буфер:

void foo() {
    ...
    SOCKET sck = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    if (sck == INVALID_SOCKET)
    {
        // error, do something...
        return;
    }
    ....
    bind(sck,(struct sockaddr *)&addr, sizeof(struct sockaddr_in));
    HANDLE hPort = CreateIoCompletionPort((HANDLE)sck, NULL, 0, 0 );
    if (!hPort)
    {
        // error, do something...
        return;
    }

    WSAOVERLAPPED Overlapped = {0};
    Overlapped.hEvent = WSACreateEvent();

    BYTE buffer[0xFFFF];
    DWORD dwBytesRecvd = 0;
    DWORD dwFlags = 0;
    sockaddr_in fromaddr = {0};
    int fromaddrlen = sizeof(fromaddr);

    WSABUF buf;
    buf.len = sizeof(buffer);
    buf.buf = buffer;

    int iRet = WSARecvFrom(sck, &buf, 1, &dwBytesRecvd, &dwFlags, (sockaddr*)&fromaddr, &fromaddrlen, &Overlapped, NULL);
    if (iRet == SOCKET_ERROR)
    {
        if (WSAGetLastError() != WSA_IO_PENDING)
        {
           // error, do something...
           return;
        }

        DWORD rBytes;
        ULONG_PTR key;
        LPOVERLAPPED pOverlapped = NULL;

        if (!GetQueuedCompletionStatus(hPort, &rbytes, &key, &pOverlapped, INFINITE))
        {
            if (pOverlapped)
            {
                // WSARecvFrom() failed...
            }
            else
            {
                // GetQueuedCompletionStatus() failed...
            }

            // do something...
            return;
        }
    }

    // I/O complete, use buffer, dwBytesRecvd, dwFlags, and fromaddr as needed...
}

Тем не менее, это противоречит цели IOCP. Если вы действительно хотите быть синхронным, вы можете просто использовать recvfrom() вместо этого, и пусть он блокирует вызывающий поток, пока не поступят данные. IOCP работает лучше всего, когда у вас есть пул потоков, обслуживающих порт завершения. Вызов WSARecvFrom() и пусть он работает в фоновом режиме, не ждите его. Пусть отдельный поток вызова GetQueuedCompletionPort() и обрабатывать данные, когда они получены, например:

struct MyOverlapped
{
    WSAOVERLAPPED overlapped;
    BYTE buffer[0xFFFF];
    DWORD buflen;
    DWORD flags;
    sockaddr_storage fromaddr;
    int fromaddrLen;
};

HANDLE hPort = NULL;

void foo() {
    ...
    SOCKET sck = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    if (sck == INVALID_SOCKET)
    {
        // error, do something...
        return;
    }
    ....
    bind(sck,(struct sockaddr *)&addr, sizeof(struct sockaddr_in));
    hPort = CreateIoCompletionPort((HANDLE)sck, NULL, 0, 0 );
    if (!hPort)
    {
        // error, do something...
        return;
    }

    MyOverlapped *ov = new MyOverlapped;
    ZeroMemory(ov, sizeof(*ov));
    ov->overlapped.hEvent = WSACreateEvent();
    ov->fromaddrlen = sizeof(ov->fromaddr);

    WSABUF buf;
    buf.len = sizeof(ov->buffer);
    buf.buf = ov->buffer;

    int iRet = WSARecvFrom(sck, &buf, 1, &ov->buflen, &ov->flags, (sockaddr*)&ov->fromaddr, &ov->fromaddrlen, (WSAOVERLAPPED*)ov, NULL);
    if (iRet == SOCKET_ERROR)
    {
        if (WSAGetLastError() != WSA_IO_PENDING)
        {
           // error, do something...
           return;
        }

        // WSARecvFrom() is now operating in the background,
        // the IOCP port will be signaled when finished...
    }
    else
    {
        // data is already available,
        // the IOCP port will be signaled immediately...
    }

    ...
}

...

// in another thread...

{
    ...

    DWORD rbytes;
    ULONG_PTR key;
    MyOverlapped *ov = NULL;

    if (!GetQueuedCompletionStatus(hPort, &rbytes, &key, (LPOVERLAPPED*)&ov, INFINITE))
    {
        if (ov)
        {
            // WSARecvFrom() failed...
            // free ov, or reuse it for another operation...
        }
        else
        {
            // GetQueuedCompletionStatus() failed...
        }
    }
    else
    {
        // use ov as needed...
        // free ov, or reuse it for another operation...
    }

    ...
}
Другие вопросы по тегам