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

时间:2013-02-08 22:09:25

标签: c linux sockets send disconnection

我正在调试基于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);

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

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

4 个答案:

答案 0 :(得分:37)

  

但如果我拔下以太网线,发送功能仍会返回   正值而不是-1。

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

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

重点是send不会发送任何内容,因此其返回代码不会告诉您实际到达另一方的数据

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

主要的一点是,在中断的情况下,TCP将在严重的沉默期后宣布连接 (在Linux上它会执行15次重试 - 超过5分钟)

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

答案 1 :(得分:2)

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

检查此主题以获取更多信息:

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

答案 2 :(得分:2)

只能通过调用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)设置较低的值,但是我说的不是精确的时间。 此外,目前您无法仅为单个插槽设置这些值。那些是全局内核参数。 有一些尝试为单个套接字(http://patchwork.ozlabs.org/patch/55236/)应用tcp重新传输套接字选项,但我不认为它已应用于内核主线。我无法在系统头文件中找到这些选项定义。

作为参考,您可以通过netstat --timers'来监控您的keepalive套接字选项。如下。 https://stackoverflow.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超时时,您可以根据您使用的平台遇到不同的返回事件,因此您不能仅通过返回事件来确定死连接状态。 例如,当发生keepalive超时时,HP返回POLLERR事件,AIX仅返回POLLIN事件。 那时你将在recv()调用中遇到ETIMEDOUT错误。

在最近的内核版本(自2.6.37开始)中,您可以使用TCP_USER_TIMEOUT选项将运行良好。此选项可用于单个插槽。

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

答案 3 :(得分:1)

检查返回值,看它是否等于此值:

  

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

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