c++selectnetwork-programmingwinsock

Do I have to call the WSAGetLastError() twice?


Platform: Windows 10 x64
          MingW64 with Visual Studio 2022

I am writing a program to add timeout function to sockets. Below is a simplified version of the code.

As shown in the following code, some code used to handle errors in the Connect function calls WSAGetLastError twice. Is this necessary?

I learned from asking GPT that this is for compatibility with multi-threaded programs, right?

#include <cmath>
#include <cstdint>
#include <stdexcept>
#include <WS2Tcpip.h>

void throw_exception(const char *function, const int code, const char *content)
{
    char message[128]{};
    snprintf(message, sizeof(message), "%s[%d]: %s", function, WSAGetLastError(), content);
    throw std::runtime_error(message);
}

class SocketType {
public:
    int family;
    int type;
    int proto;
    SOCKET fd;
    SocketType(int family, int type, int proto)
    : family(family), type(type), proto(proto)
    {
        this->fd = socket(family, type, proto);
        if(this->fd == SOCKET_ERROR) {
            throw_exception("SocketType::SocketType", WSAGetLastError(), "Failed tp create socket.");
        }
    }
};

TIMEVAL create_timeval(double number)
{
    TIMEVAL tv{};
    double intPart, floatPart;
    floatPart = modf(number, &intPart);
    tv.tv_sec = (long)intPart;
    tv.tv_usec = (long)floatPart;
    return tv;
}

void Connect(SocketType Socket, std::string remote_addr, uint16_t remote_port, double timeout)
{
    ADDRINFO hints{
                .ai_family = Socket.family,
                .ai_socktype = Socket.type,
                .ai_protocol = Socket.proto};
    ADDRINFO *result{};

    u_long mode = true;
    fd_set my_fd_set;
    TIMEVAL tv = create_timeval(timeout);

    if(getaddrinfo(remote_addr.c_str(), std::to_string(remote_port).c_str(), &hints, &result)) {
        throw_exception("Connect", WSAGetLastError(), "Failed to call getaddrinfo.");
    }

    if(ioctlsocket(Socket.fd, FIONBIO, &mode)) {
        throw_exception("Connect", WSAGetLastError(), "Failed to call ioctlsocket[1].");
    }

    int err_code;
    if(connect(Socket.fd, result->ai_addr, result->ai_addrlen) == SOCKET_ERROR) {
        if(WSAGetLastError() == WSAEWOULDBLOCK) {
            printf("WSAEWOULDBLOCK in connect() - selecting.\n");
            for(;;) {
                FD_ZERO(&my_fd_set);
                FD_SET(Socket.fd, &my_fd_set);

                err_code = select(0, nullptr, &my_fd_set, nullptr, &tv);
                if((err_code == SOCKET_ERROR) && (WSAGetLastError() != WSAEINTR)) {
                    // I called twice here
                    printf("Error: %d\n", WSAGetLastError());
                    throw_exception("Connect", WSAGetLastError(), "Failed to call select.");
                } else if(err_code) {
                    int optVal;
                    int optVal_len;

                    optVal_len = sizeof(optVal);

                    err_code = getsockopt(Socket.fd, SOL_SOCKET, SO_ERROR, (char *)&optVal, &optVal_len);
                    if(err_code == SOCKET_ERROR) {
                        // I called twice here
                        printf("Error: %d\n", WSAGetLastError());
                        throw_exception("Connect", WSAGetLastError(), "Failed to call getsockopt.");
                    }

                    if(optVal) {
                        throw_exception("Connect", optVal, "Error in delayed connection.");
                    }
                    break;
                } else {
                    throw_exception("Connect", 0, "The call to select timed out.");
                }
            }
        } else {
            // I called twice here
            printf("Error: %d\n", WSAGetLastError());
            throw_exception("Connect", WSAGetLastError(), "Failed to connecting.");
        }

        mode = false;
        if(ioctlsocket(Socket.fd, FIONBIO, &mode)) {
            throw_exception("Connect", WSAGetLastError(), "Failed to call ioctlsocket[2].");
        }
    }
}

int main()
{
    WSADATA ws;
    WSAStartup(MAKEWORD(2,2), &ws);

    try {
        SocketType fd(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        Connect(fd, "xxxxxx", 4444, 5.0);
        char send_buffer[] = {
            "GET / HTTP/1.1\r\n"
            "Host: xxxxxxx\r\n"
            "Connection: close\r\n"
            "Accept: */*\r\n"
            "User-Agent: Android\r\n"
            "\r\n"};
        char recv_buffer[4096]{};

        if(send(fd.fd, send_buffer, strlen(send_buffer), 0) == SOCKET_ERROR) {
            throw_exception("main", WSAGetLastError(), "Failed to call send.");
        }
        if(recv(fd.fd, recv_buffer, sizeof(recv_buffer) - 1, 0) == SOCKET_ERROR) {
            throw_exception("main", WSAGetLastError(), "Failed to call recv.");
        }

        printf("%s\n", recv_buffer);

    } catch (std::exception &e) {
        printf("Error! %s\n", e.what());
    }

    WSACleanup();
    return 0;
}

Solution

  • You don't need to call WSAGetLastError() multiple times, but if you did then it is basically just a wrapper for GetLastError(), which stores the last error inside the calling thread, so calling it multiple times will simply return the same value until another system call resets the last error.

    It is generally preferred to call (WSA)GetLastError() once and save the result into a local variable, and then use that as needed, eg:

    void Connect(SocketType Socket, std::string remote_addr, uint16_t remote_port, double timeout)
    {
        ...
        if (connect(...) == SOCKET_ERROR) {
            int err_code = WSAGetLastError();
            if (err_code == WSAEWOULDBLOCK) {
                ...
                int result = select(...);
                if (result == SOCKET_ERROR) {
                    err_code = WSAGetLastError();
                    printf("Error: %d\n", err_code);
                    throw_exception("Connect", err_code, "Failed to call select.");
                } else if (result > 0) {
                    ...
                    int optVal;
                    ...
                    if (getsockopt(..., (char*)&optVal, ...) == SOCKET_ERROR) {
                        err_code = WSAGetLastError();
                        printf("Error: %d\n", err_code);
                        throw_exception("Connect", err_code, "Failed to call getsockopt.");
                    }
                    if (optVal) {
                        throw_exception("Connect", optVal, "Error in delayed connection.");
                    }
                    ...
                } else {
                    throw_exception("Connect", WSAETIMEDOUT, "The call to select timed out.");
                }
            }
        }
    
        ...
    }
    

    Also, your throw_exception() is ignoring its code parameter. And, you are not doing any error checking on WSAStartup().