Paper_Lark
Paper_Lark

Reputation: 68

Socket Programming: Error on listen()

I'm working on a server part of my app and I came across a problem I can't seem to solve. The server initialization function, which is a part of ConnectionManager class, is as follows:

int ConnectionManager::init_server() {

    // Log
    OutputDebugString(L"> Initializing server...\n");

    // Initialize winsock
    WSAData wsa;
    int code = WSAStartup(MAKEWORD(2, 2), &wsa);
    if (code != 0) {
        // Error initializing winsock
        OutputDebugString(L"> Log: WSAStartup()\n");
        output_error(code);
        return -1;
    }

    // Get server information
    struct addrinfo hints, *serverinfo, *ptr;
    SOCKET sockfd = INVALID_SOCKET;
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_protocol = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    hints.ai_family = AF_UNSPEC;

    if (getaddrinfo(NULL, PORT, &hints, &serverinfo) != 0) {
        // Error when getting server address information
        OutputDebugString(L"> Log: getaddrinfo()\n");
        output_error(WSAGetLastError()); // Call Cleanup?
        return -1;
    }

    for (ptr = serverinfo; ptr != NULL; ptr = ptr->ai_next) {
        // Create socket
        if ((sockfd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol)) == INVALID_SOCKET) {
            // Error when creating a socket
            OutputDebugString(L"> Log: socket()\n");
            output_error(WSAGetLastError()); // Call Cleanup?
            continue;
        }

        // Set options
        const char enable = 1;
        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) == SOCKET_ERROR) {
            // Error when setting options
            OutputDebugString(L"> log: setsockopt()\n");
            output_error(WSAGetLastError()); // call cleanup?
            if (closesocket(sockfd) != 0) {
                output_error(WSAGetLastError());
            }
            return -1;
        }

        // Bind socket
        if (bind(sockfd, ptr->ai_addr, ptr->ai_addrlen) == SOCKET_ERROR) {
            // Error on binding
            OutputDebugString(L"> Log: bind()\n");
            output_error(WSAGetLastError()); // Call Cleanup?
            if (closesocket(sockfd) != 0) {
                output_error(WSAGetLastError());
            }
            continue;
        }    

        break;
    }
    freeaddrinfo(serverinfo);
    if (ptr == NULL) {
        OutputDebugString(L"Error: Failed to launch server.\n");
        return -1;
    }
    // Listen
    if (listen(sockfd, BACKLOG) == SOCKET_ERROR) {
        OutputDebugString(L"> Log: listen()\n");
        output_error(WSAGetLastError()); // Call Cleanup?;
        return -1;
    }
    // Accept
    struct sockaddr_storage clientinfo;
    int size = sizeof(struct sockaddr_storage);
    m_exchfd = accept(sockfd, (struct sockaddr *)&clientinfo, &size);
    if (m_exchfd = INVALID_SOCKET) {
        // Error when accepting
        OutputDebugString(L"> Log: accept()\n");
        output_error(WSAGetLastError()); // Call Cleanup?
        if (closesocket(sockfd) != 0) {
            output_error(WSAGetLastError());
        }
        return -1;
    }
    m_isConnected = true;
    return 0;
}

The output_error function simply prints the message corresponding to the error using FormatMessage() function. However, I get the following output:

> Log: listen()
> ERROR: The attempted operation is not supported for the type of object referenced.

Thus, the error should be caused by the call to listen() which is confusing. Could anyone explain me what's the cause of the problem? I bet it should be something easy to fix but I just don't seem to see it.

Upvotes: 0

Views: 2103

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 596246

The root of your problem is when calling getaddrinfo(), you are filling out the hints structure incorrectly.

You are assigning SOCK_STREAM to the ai_protocol field, and leaving the ai_socktype field set to 0. SOCK_STREAM is defined as 1, which is the same value as IPPROTO_ICMP, which is commonly used with SOCK_DGRAM sockets. So, getaddrinfo() is likely returning addrinfo entries whose ai_socktype field is set to SOCK_DGRAM. You can't use listen() on a datagram socket, thus the WSAEOPNOTSUPP error you are seeing:

If no error occurs, listen returns zero. Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be retrieved by calling WSAGetLastError.

...

WSAEOPNOTSUPP
The referenced socket is not of a type that supports the listen operation.

You need to assign SOCK_STREAM to the hints.ai_socktype field instead, and set the hints.ai_protocol field to either 0 or IPPROTO_TCP (preferably the latter).

Also, getaddrinfo() returns an error code, just like WSAStartup() does. Don't use WSAGetLastError() to get its error code.

Aside from that, I see a number of other issues with your code.

  • SO_REUSEADDR requires a BOOL (4 byte integer), not a char (1 byte). You are passing in a pointer to a single char, but are telling setsockopt() that you are passing in a pointer to an int. setsockopt() will end up trying to read a value from stack memory you don't own.

  • When your loop calls closesocket(), you should reset sockfd to INVALID_SOCKET as well, and then after the loop you should check for that condition instead of checking ptr for NULL.

  • You should be calling listen() inside your loop instead of after it. Just because you bind() a socket successfully does not guarantee you can open its assigned listening port. You should keep looping until you actually open a listening port successfully. You might also consider adding an extra log message after the loop to know which local IP/Port pair is actually listening, so you know what clients are then able to connect() to.

  • When calling WSAGetLastError(), always call it immediately after a failed Winsock call. If you call anything else beforehand, you risk resetting the error code, since WSAGetLastError() is just an alias for GetLastError(), which many APIs use.

  • When calling accept(), you are using the = assignment operator instead of the == comparison operator when checking if m_exchfd is equal to INVALID_SOCKET. Even after you fix that, if accept() succeeds, you are leaking sockfd since you lose track of it and don't call closesocket() on it. If you are expecting only 1 client to connect, close the listening socket after the client is accepted. Otherwise, store the listening socket in your class and close it after you close the accepted client socket.

With all of that said, try something more like this:

void OutputWinsockError(LPCWSTR funcName, int errCode)
{
    std::wostringstream msg;
    msg << L"> Log: " << funcName << L"()\n";
    OutputDebugStringW(msg.str().c_str());
    output_error(errCode);
}

void OutputWinsockError(LPCWSTR funcName)
{
    OutputWinsockError(funcName, WSAGetLastError());
}

int ConnectionManager::init_server() {

    // Log
    OutputDebugString(L"> Initializing server...\n");

    // Initialize winsock
    WSAData wsa;
    int err = WSAStartup(MAKEWORD(2, 2), &wsa);
    if (err != 0) {
        // Error initializing winsock
        OutputWinsockError(L"WSAStartup", err);
        return -1;
    }

    // Get server information
    struct addrinfo hints, *serverinfo, *ptr;
    SOCKET sockfd = INVALID_SOCKET;

    memset(&hints, 0, sizeof(hints));
    hints.ai_flags = AI_PASSIVE;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    err = getaddrinfo(NULL, PORT, &hints, &serverinfo);
    if (err != 0) {
        // Error when getting server address information
        OutputWinsockError(L"getaddrinfo", err);
        // Call Cleanup?
        return -1;
    }

    for (ptr = serverinfo; ptr != NULL; ptr = ptr->ai_next) {
        // Create socket
        sockfd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
        if (sockfd == INVALID_SOCKET) {
            // Error when creating a socket
            OutputWinsockError(L"socket");
            continue;
        }

        // Set options
        const BOOL enable = TRUE;
        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&enable, sizeof(BOOL)) == SOCKET_ERROR) {
            // Error when setting options
            OutputWinsockError(L"setsockopt");
            if (closesocket(sockfd) == SOCKET_ERROR) {
                // Error when closing socket
                OutputWinsockError(L"closesocket");
            }
            sockfd = INVALID_SOCKET;
            continue;
        }

        // Bind socket
        if (bind(sockfd, ptr->ai_addr, ptr->ai_addrlen) == SOCKET_ERROR) {
            // Error on binding
            OutputWinsockError(L"bind");
            if (closesocket(sockfd) == SOCKET_ERROR) {
                // Error when closing socket
                OutputWinsockError(L"closesocket");
            }
            sockfd = INVALID_SOCKET;
            continue;
        }    

        // Listen on port
        if (listen(sockfd, BACKLOG) == SOCKET_ERROR) {
            // Error on listening
            OutputWinsockError(L"listen");
            if (closesocket(sockfd) == SOCKET_ERROR) {
                // Error when closing socket
                OutputWinsockError(L"closesocket");
            }
            sockfd = INVALID_SOCKET;
            continue;
        }

        break;
    }

    freeaddrinfo(serverinfo);

    if (sockfd == INVALID_SOCKET) {
        OutputDebugString(L"Error: Failed to launch server.\n");
        // Call Cleanup?
        return -1;
    }

    // Accept
    struct sockaddr_storage clientinfo;
    int size = sizeof(clientinfo);

    m_exchfd = accept(sockfd, (struct sockaddr *)&clientinfo, &size);
    if (m_exchfd == INVALID_SOCKET) {
        // Error when accepting
        OutputWinsockError(L"accept");
        if (closesocket(sockfd) == SOCKET_ERROR) {
            OutputWinsockError(L"closesocket");
        }
        // Call Cleanup?
        return -1;
    }

    m_isConnected = true;

    // is not storing sockfd, close it

    // m_listenfd = sockfd;
    if (closesocket(sockfd) == SOCKET_ERROR) {
        OutputWinsockError(L"closesocket");
    }

    return 0;
}

Upvotes: 1

Related Questions