Linux Socket:如何在客户端程序中检测断开的网络?

Posted

技术标签:

【中文标题】Linux Socket:如何在客户端程序中检测断开的网络?【英文标题】:Linux Socket: How to detect disconnected network in a client program? 【发布时间】:2013-01-24 18:48:57 【问题描述】:

我正在调试一个基于 c 的 linux 套接字程序。正如网站上提供的所有示例一样, 我应用了以下结构:

sockfd= socket(AF_INET, SOCK_STREAM, 0);

connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr));

send_bytes = send(sockfd, sock_buff, (size_t)buff_bytes, MSG_DONTWAIT);

当删除服务器关闭其服务器程序时,我可以检测到断开连接。但是如果我拔下以太网电缆,发送函数仍然返回正值而不是 -1。

假设我无法更改服务器端,我如何检查客户端程序中的网络连接?

【问题讨论】:

【参考方案1】:

但是如果我拔掉网线,发送功能还是会返回 正值而不是 -1。

首先你应该知道send 实际上并没有发送任何东西,它只是一个内存复制函数/系统调用。它将数据从您的进程复制到内核 - 稍后内核将获取该数据并将其打包成段和数据包后将其发送到另一端。因此send 只能在以下情况下返回错误:

套接字无效(例如伪造的文件描述符) 连接显然无效,例如它尚未建立或已以某种方式终止(FIN、RST、超时 - 见下文) 没有更多空间可以复制数据

主要的一点是send 不发送任何内容,因此它的返回码不会告诉您任何有关实际到达另一端的数据

回到您的问题,当 TCP 发送数据时,它期望在合理的时间内得到有效的确认。如果它没有得到一个,它会重新发送。它多久重新发送一次?每个 TCP 堆栈做的事情都不同,但规范是使用指数退避。也就是说,先等待 1 秒,然后等待 2 秒,然后等待 4 秒,以此类推。在某些堆栈上,此过程可能需要几分钟。

主要的一点是,在中断的情况下,TCP 将声明一个连接只有在一段非常长的静默期之后(在 Linux 上它会执行 15 次重试 - 超过 5 分钟) .

解决此问题的一种方法是在您的应用程序中实现一些确认机制。例如,您可以向服务器发送请求“在 5 秒内回复,否则我将宣布此连接失效”,然后recv 超时。

【讨论】:

【参考方案2】:

要检测远程断开连接,请执行read()

查看此线程以获取更多信息:

Can read() function on a connected socket return zero bytes?

【讨论】:

问题是关于拔下以太网电缆。【参考方案3】:

您不能仅通过调用 write() 函数来检测拔出的以太网电缆。 那是因为 tcp 堆栈在您没有意识的情况下进行了 tcp 重传。 以下是解决方案。

即使您已经为您的应用程序套接字设置了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 选项会很好地工作。此选项可用于单套接字。

最后,您可以使用带有 MSG_PEEK 标志的读取函数,它可以让您检查套接字是否正常。 (MSG_PEEK 只是查看数据是否到达内核堆栈缓冲区,从不将数据复制到用户缓冲区。) 所以你可以使用这个标志来检查套接字是否正常,没有任何副作用。

【讨论】:

【参考方案4】:

检查返回值,看是否等于这个值:

EPIPE 此套接字已连接,但连接现在已断开。在这种情况下,send 先生成一个 SIGPIPE 信号;如果该信号被忽略或阻塞,或者如果它的处理程序返回,则发送失败并显示 EPIPE。

还在处理程序中添加对 SIGPIPE 信号的检查,以使其更可控。

【讨论】:

以上是关于Linux Socket:如何在客户端程序中检测断开的网络?的主要内容,如果未能解决你的问题,请参考以下文章

socket编程中,服务器如何检测到客户端网络连接的断开.比如说客户端的网线断掉了,从服务端如何能检测到呢l

Flutter)我想在socket断开时自动重新连接

java如何检测telnet服务器是不是断开

Socket如何保证长连接

如何检测Socket IO中的重新连接事件

C语言关于socket中如何判断客户端是不是与服务器保持连接