简体   繁体   中英

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;
}

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.

Yes. This is explained in man 2 recv , Return value section:

These calls return the number of bytes received, or -1 if an error occurred. In the event of an error, errno is set to indicate the error.

When a stream socket peer has performed an orderly shutdown, the return value will be 0 (the traditional "end-of-file" return).

Datagram sockets in various domains (eg, the UNIX and Internet domains) permit zero-length datagrams. When such a datagram is received, the return value is 0.

The value 0 may also be returned if the requested number of bytes to receive from a stream socket was 0.

A TCP socket is a stream socket, so as long as you make sure your recv() / read() call uses a buffer with nonzero size, a zero return indicates the peer has performed an orderly shutdown.

An orderly shutdown is one of two things: closing the socket, or calling shutdown() on it (in this case, with either SHUT_WR or SHUT_RDWR, former indicating it will not send any further data but is able to receive, and the latter indicating it will not send or receive any data).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM