Search code examples
winsock2overlapped-iogetoverlappedresult

WSAGetOverlappedResult doesn't return data from socket


Here's my code, I'd like to focus on the call to WSARecv and WSAGetOverlappedResult:

#define WIN32_LEAN_AND_MEAN

#include <winsock2.h>
#include <mswsock.h> // for ACCEPT_CONTEXT

#include <windows.h>

#include <stdio.h> // for wprintf
#include <assert.h>


static char buf[128];
static WSABUF recvBuf = {.len = 128, .buf = buf };


int WINAPI wWinMain(
        HINSTANCE hInstance __attribute__((unused)),
        HINSTANCE hPrevInstance __attribute__((unused)),
        LPWSTR pCmdLine __attribute__((unused)),
        int nCmdShow __attribute__((unused))
    ) {
    WSADATA wsaData = {0};
    if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
        wprintf(L"WSAStartup failed, quitting...\n");
        abort();
    }

    SOCKET listenSock = socket(
        AF_INET,
        SOCK_STREAM,
        IPPROTO_TCP
    );

    if (listenSock == INVALID_SOCKET) {
        wprintf(L"Invalid socket, quitting...\n");
        goto cleanupWsa;
    }

    struct sockaddr_in sockAddr = {
        .sin_family = AF_INET,
        .sin_port = htons(8080),
        .sin_addr = INADDR_ANY,
    };

    wchar_t pretty[128];
    DWORD len = sizeof pretty / sizeof(*pretty);
    if (0 != WSAAddressToString(
        (LPSOCKADDR) &sockAddr,
        sizeof sockAddr,
        NULL,
        pretty,
        &len)
    ) {
        wprintf(L"Error during AddressToString, "
            L"check needed address string length %ld, quitting...\n"
            , len);
        goto cleanupSocket;
    }

    wprintf(L"Binding to %ls\n", pretty);

    if (bind(listenSock, (LPSOCKADDR) &sockAddr, sizeof sockAddr)
        == SOCKET_ERROR) {
        wprintf(L"Bind error, quitting...\n");
        goto cleanupSocket;
    }


    if (listen(listenSock, SOMAXCONN) != 0) {
        wprintf(L"Listen error, quitting...\n");
        goto cleanupSocket;
    }


    struct sockaddr s;
    int socklen = sizeof s;
    SOCKET acceptSocket = accept(listenSock, &s, &socklen);

    wprintf(L"ACCEPT\n");

    if (SOCKET_ERROR == setsockopt(
        acceptSocket,
        SOL_SOCKET,
        SO_UPDATE_ACCEPT_CONTEXT,
        (char *)&listenSock,
        sizeof listenSock
    )) {
        wprintf(L"setsockopt update_accept_context failure\n");
        goto cleanupBothSockets;
    }
    DWORD flags = WSA_FLAG_OVERLAPPED;

    WSAOVERLAPPED w;
    memset(&w, 0, sizeof w);
    if (WSARecv(
        acceptSocket,
        &recvBuf,
        1,//dwBufferCount 
        NULL,//lpBytesReceived 
        &flags,
        &w,
        NULL
    ) != SOCKET_ERROR) {
        wprintf(L"Recv: expected SOCKET_ERROR\n");
        goto cleanupBothSockets;
    }
    int errnr = WSAGetLastError();
    if (errnr != WSA_IO_PENDING) {
        wprintf(L"RecvErr %d\n", errnr);
        goto cleanupBothSockets;
    }

    wprintf(L"Running GetOverlappedResult\n");
    DWORD bytes_transferred;
    BOOL ret = WSAGetOverlappedResult(acceptSocket, &w, &bytes_transferred, TRUE, &flags);
    if (ret == FALSE) {
        wprintf(L"OverlappedResult: error\n");
    } else {
        wprintf(L"OverlappedResult: finished with no error, bytes transferred %d", bytes_transferred);
    }

cleanupBothSockets:
    closesocket(acceptSocket);
cleanupSocket:
    closesocket(listenSock);
cleanupWsa:
    WSACleanup();
    return 0;
}

When I run the program, it listens for a connection. I connect with netcat. After connecting, it prints 'ACCEPT', as expected. It also prints 'Running GetOverlappedResult', but even though I write lots of gibberish on the netcat, neither of the "OverlappedResult:" lines appear, until I push Control-C on the netcat, and the connection dies. Then, I hit the success branch, and it says that 0 bytes were transferred.

What is wrong with this program? Why doesn't WSAGetOverlappedResult return as soon as the 128 byte buffer is filled?

I scrutinized my calls to WSARecv and WSAGetOverlappedResult. Here are some details:

  • I set the socket for WSARecv to acceptSocket, since I want to read from the concrete connection that was established.
  • I set the buffer count to one because I just have a single buffer, and C doesn't distinguish in memory between an array with one element, and just an element. So I can use the recvBuf as a one-sized array.
  • I set lpNumberOfBytesRecvd to null, since MSDN tells me to do that if I supply lpOverlapped.

Solution

  • You can't pass WSA_FLAG_OVERLAPPED to WSARecv. The socket itself is already either overlapped or not. If you pass 0 instead, the program notices the bytes from netcat.

    Furthermore, you cannot assume that WSARecv will always generate the error ERROR_IO_PENDING. The data may be available right away, in which case it will just return 0. So you need to handle both these cases as success.

    If you make the request with e.g. curl, the data may be available right away, and the program will complain that it didn't get ERROR_IO_PENDING, which you get when you manually type stuff into the socket with netcat.