Search code examples
clinuxsocketstcpposix

Is "reading zero bytes" from a socket a valid way for monitoring a TCP/IP disconnect in POSIX C?


I'm currently reviewing a C application that implements POSIX sockets. This application intermittently checks to see if the connection to the server is valid by reading zero bytes from the socket. It then checks to see if an errno is set to determine whether or not the connection is okay. Is this a robust solution?

uint32_t IsConnected()
{
        char dummy[10];
        if(read(global_sockfd, dummy, 0) == -1)
        {
            if(errno != EWOULDBLOCK && errno != EAGAIN)
                return FALSE;
            else
                return TRUE;
        }   
        else
            return TRUE;
}

Solution

  • No, this is not a robust solution, for two reasons.

    First, for a connected TCP socket read and recv will return zero, not −1, after all incoming data has been read and the remote peer has closed its end of the connection (using close or shutdown). Your IsConnected will return TRUE in this case, which is wrong.

    Second, the specification of read says (second paragraph of DESCRIPTION; all emphasis mine)

    Before any action described below is taken, and if nbyte is zero, the read function may detect and return errors as described below. In the absence of errors, or if error detection is not performed, the read function shall return zero and have no other results.

    nbyte is the third argument, which your IsConnected is supplying as zero. Therefore, depending on the operating system, IsConnected might always return TRUE, regardless of the state of the socket.

    The specification of recv does not say anything about what happens if the length argument (equivalent to nbyte for read) is zero; I think this is probably an oversight and it (and recvfrom, recvmsg, etc.) is meant to have the same special behavior as read. So changing read to recv will not fix the problem by itself. However, I think a complete fix is possible by using recv with MSG_PEEK:

    bool is_connected(int sock)
    {
        char dummy[1];
        ssize_t nread = recv(sock, dummy, sizeof dummy, MSG_PEEK);
        if (nread > 0)
            return true;    // at least one byte of data available
        else if (nread == 0)
            return false;   // EOF
        else
            return errno == EWOULDBLOCK || errno == EAGAIN;
    }
    

    Using MSG_PEEK allows you to supply a nonzero length, because the data will not actually be consumed.

    Depending on the details of the application and its network protocol, you may also want to consider enabling TCP keep-alive packets on the socket.