Search code examples
c++winsock2

How to use select() to set a timer for sockets?


I'm currently using Winsock2 to be able to test a connection to multiple local telnet servers, but if the server connection fails, the default Winsock client takes forever to timeout.

I've seen from other posts that select() can set a timeout for the connection part, and that setsockopt() with timeval can timeout the receiving portion of the code, but I have no idea how to implement either. Pieces of code that I've copy/pasted from other answers always seem to fail for me.

How would I use both of these functions in the default client code? Or, if it isn't possible to use those functions in the default client code, can someone give me some pointers on how to use those functions correctly?


#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctime>

// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")


#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "23"



int __cdecl main(int argc, char** argv)
{
    WSADATA wsaData;
    SOCKET ConnectSocket = INVALID_SOCKET;
    struct addrinfo* result = NULL,
        * ptr = NULL,
        hints;
    const char* sendbuf = "this is a test";
    char recvbuf[DEFAULT_BUFLEN];
    int iResult;
    int recvbuflen = DEFAULT_BUFLEN;

    // Validate the parameters
    if (argc != 2) {
        printf("usage: %s server-name\n", argv[0]);
        return 1;
    }

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    /*
    struct timeval timeout;
    timeout.tv_sec = 10;
    timeout.tv_usec = 0;

    if (setsockopt(ConnectSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout,
        sizeof(timeout)) < 0) {
        printf("setsockopt failed\n");
    }
    */

    




    // Resolve the server address and port
    iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    }





    // Attempt to connect to an address until one succeeds
    for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {

        // Create a SOCKET for connecting to server
        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
            ptr->ai_protocol);
        if (ConnectSocket == INVALID_SOCKET) {
            printf("socket failed with error: %ld\n", WSAGetLastError());
            WSACleanup();
            return 1;
        }

        // Connect to server.
        iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        }
        break;
    }

    freeaddrinfo(result);

    if (ConnectSocket == INVALID_SOCKET) {
        printf("Unable to connect to server!\n");
        WSACleanup();
        return 1;
    }

    // Send an initial buffer
    iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
    if (iResult == SOCKET_ERROR) {
        printf("send failed with error: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    printf("Bytes Sent: %ld\n", iResult);

    // shutdown the connection since no more data will be sent
    iResult = shutdown(ConnectSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown failed with error: %d\n", WSAGetLastError());
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    // Receive until the peer closes the connection
    do {

        iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0)
            printf("Bytes received: %d\n", iResult);
        else if (iResult == 0)
            printf("Connection closed\n");
        else
            printf("recv failed with error: %d\n", WSAGetLastError());

    } while (iResult > 0);

    // cleanup
    closesocket(ConnectSocket);
    WSACleanup();

    return 0;
}

Solution

  • select() can set a timeout for the connection part.

    Yes, but only if you put the socket into non-blocking mode before calling connect(), so that connect() exits immediately and then the code can use select() to wait for the socket to report when the connect operation has finished. But the code shown is not doing that.

    setsockopt() with timeval can timeout the receiving portion of the code

    Yes, though select() can also be used to timeout a read operation, as well. Simply call select() first, and then call recv() only if select() reports that the socket is readable (has pending data to read).

    Try something like this:

    #define WIN32_LEAN_AND_MEAN
    
    #include <windows.h>
    #include <winsock2.h>
    #include <ws2tcpip.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <ctime>
    
    // Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib
    #pragma comment (lib, "Ws2_32.lib")
    #pragma comment (lib, "Mswsock.lib")
    #pragma comment (lib, "AdvApi32.lib")
    
    #define DEFAULT_BUFLEN 512
    #define DEFAULT_PORT "23"
    
    int __cdecl main(int argc, char** argv)
    {
        WSADATA wsaData;
        SOCKET ConnectSocket = INVALID_SOCKET;
        struct addrinfo *result = NULL,
            *ptr = NULL,
            hints;
        const char* sendbuf = "this is a test";
        char recvbuf[DEFAULT_BUFLEN];
        int iResult;
        int recvbuflen = DEFAULT_BUFLEN;
    
        // Validate the parameters
        if (argc != 2) {
            printf("usage: %s server-name\n", argv[0]);
            return 1;
        }
    
        // Initialize Winsock
        iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
        if (iResult != 0) {
            printf("WSAStartup failed with error: %d\n", iResult);
            return 1;
        }
    
        ZeroMemory(&hints, sizeof(hints));
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_protocol = IPPROTO_TCP;
    
        // Resolve the server address and port
        iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
        if (iResult != 0) {
            printf("getaddrinfo failed with error: %d\n", iResult);
            WSACleanup();
            return 1;
        }
    
        // Attempt to connect to an address until one succeeds
        for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {
    
            // Create a SOCKET for connecting to server
            ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
            if (ConnectSocket == INVALID_SOCKET) {
                printf("socket failed with error: %ld\n", WSAGetLastError());
                WSACleanup();
                return 1;
            }
    
            // put the socket into non-blocking mode just for connect()
            int iMode = 1;
            iResult = ioctlsocket(ConnectSocket, FIONBIO, &iMode);
            if (iResult == SOCKET_ERROR)
                closesocket(ConnectSocket);
                ConnectSocket = INVALID_SOCKET;
                continue;
            }
    
            // Connect to server.
            iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
            if (iResult == SOCKET_ERROR) {
                if (WSAGetLastError() != WSAEWOULDBLOCK) {
                    closesocket(ConnectSocket);
                    ConnectSocket = INVALID_SOCKET;
                    continue;
                }
    
                // wait for the connect to finish
                fd_set fds;
                FD_ZERO(&fds);
                FD_SET(ConnectSocket, &fds);
    
                struct timeval timeout;
                timeout.tv_sec = 10;
                timeout.tv_usec = 0;
    
                iResult = select(0, NULL, &fds, NULL, &timeout);
                if (iResult <= 0) {
                    closesocket(ConnectSocket);
                    ConnectSocket = INVALID_SOCKET;
                    continue;
                }
            }
    
            // put the socket back into blocking mode
            iMode = 0;
            iResult = ioctlsocket(ConnectSocket, FIONBIO, &iMode);
            if (iResult == SOCKET_ERROR) {
                closesocket(ConnectSocket);
                ConnectSocket = INVALID_SOCKET;
                continue;
            }
    
            break;
        }
    
        freeaddrinfo(result);
    
        if (ConnectSocket == INVALID_SOCKET) {
            printf("Unable to connect to server!\n");
            WSACleanup();
            return 1;
        }
    
        struct timeval timeout;
        timeout.tv_sec = 10;
        timeout.tv_usec = 0;
    
        // set a read timeout
        if (setsockopt(ConnectSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) == SOCKET_ERROR) {
            printf("setsockopt failed\n");
        }
    
        // Send an initial buffer
        iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
        if (iResult == SOCKET_ERROR) {
            printf("send failed with error: %d\n", WSAGetLastError());
            closesocket(ConnectSocket);
            WSACleanup();
            return 1;
        }
    
        printf("Bytes Sent: %ld\n", iResult);
    
        // shutdown the connection since no more data will be sent
        iResult = shutdown(ConnectSocket, SD_SEND);
        if (iResult == SOCKET_ERROR) {
            printf("shutdown failed with error: %d\n", WSAGetLastError());
            closesocket(ConnectSocket);
            WSACleanup();
            return 1;
        }
    
        // Receive until the peer closes the connection
        do {
            /*
            fd_set fds;
            FD_ZERO(&fds);
            FD_SET(ConnectSocket, &fds);
    
            struct timeval timeout;
            timeout.tv_sec = 10;
            timeout.tv_usec = 0;
    
            iResult = select(0, &fds, NULL, NULL, &timeout);
            if (iResult <= 0) {
                if (iResult == SOCKET_ERROR)
                    printf("select failed with error: %d\n", WSAGetLastError());      
                else
                    printf("select timed out\n");
                break;
            }
            */
    
            iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
            if (iResult > 0)
                printf("Bytes received: %d\n", iResult);
            else if (iResult == 0)
                printf("Connection closed\n");
            else if (WSAGetLastError() == WSAETIMEDOUT)
                printf("recv timed out\n");
            else
                printf("recv failed with error: %d\n", WSAGetLastError());
    
        } while (iResult > 0);
    
        // cleanup
        closesocket(ConnectSocket);
        WSACleanup();
    
        return 0;
    }