Search code examples
socketstcptimeoutwinsockblocking

timeout issue for connecting via blocking / nonblocking TCP sockets in C/C++


I'm searching for an efficient way in C/C++ to check if a server is reachable on a specific port. Basically the idea is to open a socket on the client by socket and connect to the server on this port by connect.

When using a blocking socket on a server which is not reachable at all I have to wait until the default timeout is reached. In case the server is working, but the port is not exposed connect returns immediately with SOCKET_ERROR.

SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int rc = connect(s, (SOCKADDR*)&target, sizeof(target));
closesocket(s);

if (rc == SOCKET_ERROR)
    return false;
else
    return true

When using a non blocking socket on a server which is not reachable at all, I have the same behavior as for the situation that the port is not exposed. I always have to wait for the defined timeout (which is 10 sec in the following example).

// open socket
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

// set mode to unblocking
unsigned long mode = 1;
int rc = ioctlsocket(s, FIONBIO, &mode);

rc = connect(s, (SOCKADDR*)&target, sizeof(target));

struct timeval tv;
tv.tv_sec = 10;
tv.tv_usec = 0;

fd_set read;
FD_ZERO(&read);
FD_SET(s, &read);

fd_set write = read;    


rc = select(NULL, &read, NULL, NULL, &tv);
closesocket(s);


if (rc == 0 || rc == SOCKET_ERROR)
    return false;

else
    return true;

So the question is if there is a way to combine the advantages of both approaches. That means getting an immediate result if the server works, but does not expose the port and if the server does not work get this information after a specified timeout.

I was trying to use a blocking socket with the options SO_RCVTIMEO and SO_SNDTIMEO. That did not work either and since I like to probe for different server / ports in parallel the non blocking approach is more convenient for me.


Solution

  • If the server is reachable, but the port is not accessible, a non-blocking socket would fail immediately, not wait for the full timeout. But you are not detecting that because you are using select() the wrong way.

    You are waiting on the readfds parameter. If a non-blocking socket fails to connect, select() will not report the socket is readable. And if the socket succeeds in connecting, the socket will not be reported as readable until the server sends bytes to your client.

    To properly detect the result of a non-blocking connect(), you need to use the writefds and exceptfds parameters of select(). This is clearly stated in the select() documentation on MSDN:

    In summary, a socket will be identified in a particular set when select returns if:

    readfds:

    • If listen has been called and a connection is pending, accept will succeed.
    • Data is available for reading (includes OOB data if SO_OOBINLINE is enabled).
    • Connection has been closed/reset/terminated.

    writefds:

    • If processing a connect call (nonblocking), connection has succeeded.
    • Data can be sent.

    exceptfds:

    • If processing a connect call (nonblocking), connection attempt failed.
    • OOB data is available for reading (only if SO_OOBINLINE is disabled).

    In the case that a non-blocking connect() fails, you have to use getsockopt() with the SO_ERROR opcode to get the actual error code.

    Try something more like this:

    // open socket
    SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (s == INVALID_SOCKET)
    {
        // handle error...
        return false;
    }
    
    // set mode to unblocking
    unsigned long mode = 1;
    int rc = ioctlsocket(s, FIONBIO, &mode);
    if (rc == SOCKET_ERROR)
    {
        // handle error...
        closesocket(s);
        return false;
    }
    
    rc = connect(s, (SOCKADDR*)&target, sizeof(target));
    if ((rc == SOCKET_ERROR) && (WSAGetLastError() == WSAEWOULDBLOCK))
    {
        fd_set write, except;
    
        FD_ZERO(&write);
        FD_SET(s, &write);
    
        FD_ZERO(&except);
        FD_SET(s, &except);
    
        struct timeval tv;
        tv.tv_sec = 10;
        tv.tv_usec = 0;
    
        rc = select(NULL, NULL, &write, &except, &tv);
        if (rc == 0)
        {
            WSASetLastError(WSAETIMEDOUT);
            rc = SOCKET_ERROR;
        }
        else if (rc > 0)
        {
            if (FD_ISSET(s, &except))
            {
                int err = 0;
                getsockopt(s, SOL_SOCKET, SO_ERROR, (char*)&err, sizeof(err));
                WSASetLastError(err);
                rc = SOCKET_ERROR;
            }
            else
                rc = 0;
        }
    }
    
    if (rc == SOCKET_ERROR)
    {
        // handle error...
        clossesocket(s);
        return false;
    }
    
    closesocket(s);
    return true;