使用TCP Keep-Alive在写入受阻塞的套接字上获取断开连接通知

5

我使用TCP Keep-Alive选项来检测死连接。它可以很好地与使用读取套接字的连接配合使用:

setsockopt(mysock,...) // set various keep alive options

epoll_ctl(ep,mysock,{EPOLLIN|EPOLERR|EPOLLHUP},)
epoll_wait -> (exits after several seconds when remove host disconnects cable)

Epoll等待在没有问题的情况下以EPOLLIN | EPOLLHUP退出套接字。

但是,如果我尝试向套接字写入很多数据,直到出现EAGAIN,然后轮询读取和写入,当我断开电缆时,我不会收到错误:

setsockopt(mysock,...) // set various keep alive options

while(send() != EAGAIN)
   ;
epoll_ctl(ep,mysock,{EPOLLIN|EPOLLOUT|EPOLERR|EPOLLHUP},)
epoll_wait -> --- Never exits!!!! even when the cable of the remove host is disconnected!!!
  • 这个问题怎么解决?
  • 有人见过类似的问题吗?
  • 有什么可能的方向吗?

编辑: 附加信息

当我使用wireshark监控通信时,在第一种情况(读取)中,每隔几秒钟我会收到一次ack请求。但在第二种情况下,我根本没有检测到。


TCP keep-alive 在两个小时后才会启动,而不是几秒钟。您确定您指的不是“读取超时”吗? - user207421
@EJP,有修改保持连接时间的选项 - 这就是我在setsockopts中所做的...这样它可以在几秒钟内检测到问题。因此,如果我在远程端拔掉电缆并进行读取轮询套接字(第一个示例),则可以在几秒钟内检测到断开连接...但在第二种情况下不行。 - Artyom
当调用 epoll_ctl(...) 时,您是否检查错误? - user2343912
这是在哪个操作系统上?这个级别很低,即使在Linux中的行为也可能会有所不同。 - Old Pro
RHEL 6,使用 epoll_ctl 是可以的。 - Artyom
3个回答

15
如果在所有数据传输完成之前断开网络连接,则该连接不会处于空闲状态,因此在某些实现中,保持连接计时器不会启动。(请记住,保持连接并非TCP规范的一部分,因此如果有实现,它的实现方式是不一致的。)通常情况下,由于指数退避和大量重试(tcp_retries2默认为15),可能需要最多30分钟的传输重试超时,然后才会启动保持连接计时器。
解决方法取决于您使用的特定TCP实现。一些新版本的Linux(内核版本2.6.37于2011年1月4日发布)实现了TCP_USER_TIMEOUT。更多信息请参见这里
通常建议在应用程序层面实现通信超时,而不是使用基于TCP的保持连接。例如,参见HTTP Keep-Alive

还有可能有一种方法来定义tcp_retries的数量吗? - Artyom
tcp_retries2是一个内核级别的变量,适用于所有连接,而不是每个连接的变量,因此您可能不想去修改它。 - Old Pro
1
谢谢!将 tcp_retries2 的值改为较低的数值可以解决这个问题! - Artyom

0
即使您已将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://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)

此外,当保持连接超时发生时,您可能会遇到不同的返回事件,这取决于您使用的平台,因此您不能仅通过返回事件来确定死连接状态。 例如,HP返回POLLERR事件,而AIX在保持连接超时发生时只返回POLLIN事件。 此时,在recv()调用中会遇到ETIMEDOUT错误。
在最近的内核版本(自2.6.37以来),您可以使用TCP_USER_TIMEOUT选项。此选项可用于单个套接字。

0
我想提几点... 1) 根据this document,在Linux中使用keepalive需要以下内容:

Linux has built-in support for keepalive. You need to enable TCP/IP networking in order to use it. You also need procfs support and sysctl support to be able to configure the kernel parameters at runtime.

The procedures involving keepalive use three user-driven variables:

tcp_keepalive_time 

> the interval between the last data packet sent (simple ACKs are not considered data) and the first keepalive probe; after the connection is marked to need keepalive, this counter is not used any further

tcp_keepalive_intvl 

> the interval between subsequential keepalive probes, regardless of what the connection has exchanged in the meantime

tcp_keepalive_probes 

> the number of unacknowledged probes to send before considering the connection dead and notifying the application layer

Remember that keepalive support, even if configured in the kernel, is not the default behavior in Linux. Programs must request keepalive control for their sockets using the setsockopt interface. There are relatively few programs implementing keepalive, but you can easily add keepalive support for most of them following the instructions explained later in this document.

尝试查看当前系统中这些变量的当前值,以确保它们是正确的或有意义的。粗体突出是我的,看起来你正在这样做。

我假设这些变量的值是以毫秒为单位的,但不确定,请再次确认。

tcp_keepalive_time

我期望一个值,意思大约是“在最后一个数据包发送后尽快发送第一个探测包”。
tcp_keepalive_intvl 

我猜这个变量的值应该比TCP默认关闭连接所需的时间少。

tcp_keepalive_probes 

这可能是决定你的应用程序成败的“魔法值”;如果未确认探测的数量过高,可能是导致epoll_wait() 永远不会退出的原因。

该文档讨论了Linux内核版本(2.4.x、2.6.x)中TCP keepalive的实现,以及如何在C语言中编写启用TCP keepalive的应用程序。

http://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/

2) 确保在 epoll_wait() 的超时参数中不指定 -1,因为这会导致 epoll_wait() 无限期地阻塞。

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

timeout参数指定了epoll_wait()函数将会阻塞的最小毫秒数。(这个时间间隔将会被舍入到系统时钟粒度,并且内核调度延迟意味着阻塞时间可能会超过一小段时间。)指定timeout为-1会导致epoll_wait()无限期地阻塞,而指定timeout等于零会导致epoll_wait()立即返回,即使没有事件可用。来自手册页面http://linux.die.net/man/2/epoll_wait

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接