Search code examples
c++windowsnetwork-programmingtcpclient

How to determine if a connect with a Winsock timeout has succeeded


I used the following C++ code to create a TCP/IP client. In actual use, I use it in a loop and keep checking for connections until the server is ready.

This code keeps looping through the order 0.2, 0.6, 1 before connecting to the server.

After a successful connection to the server, nothing is printed. No errors are printed, and almost nowhere is passed.

How do I know TCP/IP made a successful connection?

int connect_timeout(int & socket, struct sockaddr * name, int namelen, timeval timeout)
{
    unsigned long mode = 1;
    int result= ioctlsocket(socket, FIONBIO, &mode);
    if(NO_ERROR != result)
    {
        //std::cout << "0.0---------------" << std::endl;
        return -1;
    }
    else
    {
        //std::cout << "0.1---------------" << std::endl;
    }

    result= connect(socket, name, namelen);
    if(SOCKET_ERROR == result)
    {
        int errono = WSAGetLastError();
        if(WSAEWOULDBLOCK == errono)
        {
            std::cout << "0.2---------------" << std::endl;
            errono = 0;
        }
        else
        {
            //std::cout << "0.3---------------" << std::endl;
            mode = 0;
            ioctlsocket(socket, FIONBIO, &mode);
            return -1;
        }
    }
    else
    {
        std::cout << "0.4---------------" << std::endl;
    }

    mode = 0;
    result= ioctlsocket(socket, FIONBIO, &mode);
    if(0 < result)
    {
        //error
        std::cout << "0.5---------------" << std::endl;
        return -1;
    }
    else
    {
        std::cout << "0.6---------------" << std::endl;
    }
    
    fd_set readFd, writeFd, errFd;
    FD_ZERO(&readFd);
    FD_ZERO(&writeFd);
    FD_ZERO(&errFd);
    FD_SET(socket, &readFd);
    FD_SET(socket, &writeFd);
    FD_SET(socket, &errFd);
    int sockNum = select(socket + 1, &readFd, &writeFd, &errFd, &timeout);
    if(0 == sockNum)
    {
        //timeout
        std::cout << "1---------------" << std::endl;
        return -1;
    }
    else if(FD_ISSET(socket, &readFd) || FD_ISSET(socket, &writeFd) )
    {
        std::cout << "2---------------" << std::endl;
    }
    else
    {
        //error
        std::cout << "3---------------" << std::endl;
        return -1;
    }
    std::cout << "4---------------" << std::endl;

    return 0;
}

Solution

  • The connect() documentation says:

    With a nonblocking socket, the connection attempt cannot be completed immediately. In this case, connect will return SOCKET_ERROR, and WSAGetLastError will return WSAEWOULDBLOCK. In this case, there are three possible scenarios:

    • Use the select function to determine the completion of the connection request by checking to see if the socket is writable.
    • ...

    Until the connection attempt completes on a nonblocking socket, all subsequent calls to connect on the same socket will fail with the error code WSAEALREADY, and WSAEISCONN when the connection completes successfully. Due to ambiguities in version 1.1 of the Windows Sockets specification, error codes returned from connect while a connection is already pending may vary among implementations. As a result, it is not recommended that applications use multiple calls to connect to detect connection completion. If they do, they must be prepared to handle WSAEINVAL and WSAEWOULDBLOCK error values the same way that they handle WSAEALREADY, to assure robust operation.

    ...

    For connection-oriented, nonblocking sockets, it is often not possible to complete the connection immediately. In such a case, this function returns the error WSAEWOULDBLOCK. However, the operation proceeds.

    When the success or failure outcome becomes known, it may be reported in one of two ways, depending on how the client registers for notification.

    • If the client uses the select function, success is reported in the writefds set and failure is reported in the exceptfds set.
    • ...

    And the select() documentation says:

    The parameter writefds identifies the sockets that are to be checked for writability. If a socket is processing a connect call (nonblocking), a socket is writable if the connection establishment successfully completes. ...

    The parameter exceptfds identifies the sockets that are to be checked for the presence of OOB data or any exceptional error conditions.

    Note Out-of-band data will only be reported in this way if the option SO_OOBINLINE is FALSE. If a socket is processing a connect call (nonblocking), failure of the connect attempt is indicated in exceptfds (application must then call getsockopt SO_ERROR to determine the error value to describe why the failure occurred). This document does not define which other errors will be included.

    You are turning off the socket's non-blocking mode before you are calling select(). You need to wait for the non-blocking connect() operation to complete before you can then mess around with the socket's settings.

    Try something more like this instead:

    int connect_timeout(int & socket, struct sockaddr * name, int namelen, timeval timeout)
    {
        u_long mode = 1;
        int errNo;
    
        int result = ioctlsocket(socket, FIONBIO, &mode);
        if (SOCKET_ERROR == result)
        {
            errNo = WSAGetLastError();
            std::cout << "Unable to set socket to non-blocking mode. Error: " << errNo << std::endl;
            return -1;
        }
    
        std::cout << "Connecting..." << std::endl;
    
        result = connect(socket, name, namelen);
        if (SOCKET_ERROR == result)
        {
            errNo = WSAGetLastError();
            if (WSAEWOULDBLOCK == errNo)
            {
                fd_set writeFd, errFd;
                FD_ZERO(&writeFd);
                FD_ZERO(&errFd);
                FD_SET(socket, &writeFd);
                FD_SET(socket, &errFd);
    
                result = select(socket + 1, NULL, &writeFd, &errFd, &timeout);
                if (SOCKET_ERROR == result)
                {
                    errNo = WSAGetLastError();
                }
                else if (0 == result)
                {
                    result = SOCKET_ERROR;
                    errNo = WSAETIMEDOUT;
                }
                else if (FD_ISSET(socket, &errFd))
                {
                    result = SOCKET_ERROR;
                    errNo = 0;
                    int len = sizeof(errNo);
                    getsockopt(socket, SOL_SOCKET, SO_ERROR, (char*)&errNo, &len);
                }
                else
                {
                    result = errNo = 0;
                }
            }
    
            if (SOCKET_ERROR == result)
            {
                if (WSAETIMEDOUT == errNo)
                    std::cout << "Connection timed out" << std::endl;
                else
                    std::cout << "Unable to connect. Error: " << errNo << std::endl;
    
                mode = 0;
                ioctlsocket(socket, FIONBIO, &mode);
                return -1;
            }
        }
    
        std::cout << "Connected" << std::endl;
    
        mode = 0;
        result = ioctlsocket(socket, FIONBIO, &mode);
        if (SOCKET_ERROR == result)
        {
            errNo = WSAGetLastError();
            std::cout << "Unable to set socket to blocking mode. Error: " << errNo << std::endl;
            return -1;
        }
    
        return 0;
    }