来自套接字的 "reading zero bytes" 是在 POSIX C 中监视 TCP/IP 断开连接的有效方法吗?

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

我目前正在审查实现 POSIX 套接字的 C 应用程序。此应用程序通过从套接字读取零字节来间歇性地检查与服务器的连接是否有效。然后它检查是否设置了 errno 以确定连接是否正常。这是一个可靠的解决方案吗?

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

是的。这在 man 2 recvReturn 值 部分进行了解释:

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 (e.g., 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.

TCP 套接字是流式套接字,因此只要确保您的 recv()/read() 调用使用非零大小的缓冲区,零 return 表示对等方已执行有序关闭。

有序关闭是两件事之一:关闭套接字,或对其调用 shutdown()(在这种情况下,使用 SHUT_WR 或 SHUT_RDWR,前者表示它将不发送任何进一步的数据但能够接收,后者表示它不会发送或接收任何数据)。

不,这不是一个可靠的解决方案,原因有两个。

首先,对于已连接的 TCP 套接字,readrecv 将 return 为零,而不是 -1,在读取所有传入数据并且远程对等方已关闭其末端之后连接(使用 closeshutdown)。在这种情况下,您的 IsConnected 将 return 为真,这是错误的。

其次,specification of read 说(描述的第二段;全部强调我的)

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 是第三个参数,您的 IsConnected 提供的参数为零。因此,根据操作系统的不同,IsConnected 可能 总是 return TRUE,无论套接字的状态如何。

specification of recv 没有说明如果 length 参数(相当于 readnbyte)为零时会发生什么;我认为这可能是一个疏忽,它(以及 recvfromrecvmsg 等)应该具有与 read 相同的特殊行为。因此,将 read 更改为 recv 本身并不能解决问题。但是,我认为可以通过将 recvMSG_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;
}

使用 MSG_PEEK 允许您提供非零长度,因为数据实际上不会被消耗。

根据应用程序及其网络协议的详细信息,您可能还需要考虑套接字上的 enabling TCP keep-alive packets