如何检测 TCP 套接字断开连接(使用 C Berkeley 套接字)

Posted

技术标签:

【中文标题】如何检测 TCP 套接字断开连接(使用 C Berkeley 套接字)【英文标题】:How to detect a TCP socket disconnection (with C Berkeley socket) 【发布时间】:2011-09-18 05:47:29 【问题描述】:

我正在使用循环从 c Berkeley 套接字读取消息,但我无法检测到套接字何时断开,因此我将接受新连接。请帮忙

while(true) 
            bzero(buffer,256);
            n = read(newsockfd,buffer,255);
            printf("%s\n",buffer);        

【问题讨论】:

【参考方案1】:

您可以检测到套接字已连接的唯一方法是写入它。

read()/recv() 上出现错误表示连接已断开,但读取时未出现错误并不意味着连接已启动。

您可能有兴趣阅读以下内容: http://lkml.indiana.edu/hypermail/linux/kernel/0106.1/1154.html

此外,使用TCP Keep Alive 可能有助于区分非活动连接和断开连接(通过定期发送某些内容,即使应用程序没有要发送的数据)。

(编辑:删除了@Damon 指出的错误句子,谢谢。)

【讨论】:

轻微修正:“读取 0 字节也可能仅仅意味着远程方没有任何要发送的东西,不一定是问题” -- 接收 0 字节表示另一端彻底关闭了连接。如果另一端没有任何东西要发送,如果套接字是非阻塞的,你会得到 EAGAIN 或 EWOULDBLOCK,否则它会阻塞直到数据到达。检测连接断开的一种方法是在 epoll 上注册 EPOLLHUP 和 EPOLLRDHUP。这不是 100% 可靠的,但会报告有序的关闭、半关闭和丢失的保活。 @Pacerier,因为没有发送任何内容而没有得到任何东西,因为链接断开而没有得到任何东西没有区别。 (其实这是一个普遍的原则。如果你早上没有收到任何信件,要么没有人给你寄任何东西,要么邮政系统不工作:你只能通过尝试发送一些东西来找出原因。发布,或多或少。)这就是为什么您需要在使用 TCP 连接时处理超时。突然关闭的连接根本不会向您发送任何信息来告诉您它正在关闭,因为它不能。 @jean,当然可能会有延迟,但这仍然是检测断开连接的唯一方法。如果您需要尽快知道,请冲洗缓冲区。 (如果您正在等待读取错误,您可以等待很长时间......这并不可靠。) @jean "如果是突然断线,用write也无能为力吧?" 不,不是无能为力,应该fail,因此您可以检测到断开连接。您期望每 15 秒接收一次心跳的想法是不同的,在没有关闭连接的情况下可能会有更长的延迟。然后,您就可以任意决定您认为可以接受的延迟,仅此而已(实际上这通常是正确的做法),但这实际上并不能告诉您套接字是否已断开连接,这仅意味着您放弃了并假设它有。 @jean,是的,从这个意义上说是突然的(完全丢包而没有警告)。当没有任何东西要发送时,您不知道您是否要接收某些东西(因此,正如您所建议的那样,协议中的心跳的想法,对于支持它的协议)。找出它是否断开连接的唯一方法是尝试写入。尝试阅读某些内容,无论您是否不知道它是否意味着发送任何内容都不会告诉您任何事情。由于您使用的是异步写入,因此您的情况有所不同。我不太了解 boost.asio,但如果可以的话,请尝试立即刷新缓冲区。【参考方案2】:

您的问题是您完全忽略了read() 返回的结果。 read() 之后的代码至少应该是这样的:

if (n == 0) // peer disconnected
    break;
else if (n == -1) // error

    perror("read");
    break;

else // received 'n' bytes

    printf("%.*s", n, buffer);

并且接受一个新的连接应该在一个单独的线程中完成,而不依赖于这个连接上的流结束。

bzero() 调用毫无意义,只是先前错误的解决方法。

【讨论】:

【参考方案3】:

那是因为你没有使用 keepalive 超时。 在接收端,keepalive socket选项是检测死连接的最佳方案。

但是,如果您的应用程序继续写入套接字,则需要考虑更多。 即使您已经为您的应用程序套接字设置了keepalive选项,您也无法及时检测到套接字的死连接状态,以防您的应用程序一直在套接字上写入。 那是因为内核 tcp 堆栈的 tcp 重传。 tcp_retries1 和 tcp_retries2 是用于配置 tcp 重传超时的内核参数。 重传超时的精确时间很难预测,因为它是通过 RTT 机制计算的。 你可以在 rfc793 中看到这个计算。 (3.7. 数据通信)

https://www.rfc-editor.org/rfc/rfc793.txt

每个平台都有用于 tcp 重传的内核配置。

Linux : tcp_retries1, tcp_retries2 : (exist in /proc/sys/net/ipv4)

http://linux.die.net/man/7/tcp

HPUX : tcp_ip_notify_interval, tcp_ip_abort_interval

http://www.hpuxtips.es/?q=node/53

AIX : rto_low, rto_high, rto_length, rto_limit

http://www-903.ibm.com/kr/event/download/200804_324_swma/socket.pdf

如果您想及早检测到死连接,您应该为 tcp_retries2 设置较低的值(默认为 15),但这不是我已经说过的精确时间。 此外,目前您不能只为单个套接字设置这些值。这些是全局内核参数。 有一些尝试为单套接字应用 tcp 重传套接字选项(http://patchwork.ozlabs.org/patch/55236/),但我认为它没有应用于内核主线。我在系统头文件中找不到这些选项定义。

作为参考,您可以通过如下所示的“netstat --timers”监控您的 keepalive 套接字选项。 https://***.com/questions/34914278

netstat -c --timer | grep "192.0.0.1:43245             192.0.68.1:49742"

tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (1.92/0/0)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (0.71/0/0)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (9.46/0/1)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (8.30/0/1)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (7.14/0/1)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (5.98/0/1)
tcp        0      0 192.0.0.1:43245             192.0.68.1:49742            ESTABLISHED keepalive (4.82/0/1)

另外,当keepalive超时发生时,你可以根据你使用的平台,遇到不同的返回事件,所以你不能仅仅通过返回事件来决定死连接状态。 例如,HP 返回 POLLERR 事件,而 AIX 在发生 keepalive 超时时仅返回 POLLIN 事件。 届时您将在 recv() 调用中遇到 ETIMEDOUT 错误。

在最近的内核版本中(自 2.6.37 起),您可以使用 TCP_USER_TIMEOUT 选项会很好地工作。此选项可用于单套接字。

【讨论】:

不,不是。这是因为他没有检查错误甚至是流的结束。他完全忽略了reD()返回的结果。

以上是关于如何检测 TCP 套接字断开连接(使用 C Berkeley 套接字)的主要内容,如果未能解决你的问题,请参考以下文章

c语言如何检测网络是不是连接

C TCP无法检测到断开的连接[重复]

立即检测客户端与服务器套接字的断开连接

如何在C中编写一个函数来断开客户端与服务器的连接? [复制]

SocketIO + Flask 检测断开连接

TCP/IP网络编程:07优雅地断开套接字连接