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

我使用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的通信时,在第一种情况下(阅读),我在几秒钟内请求确认。 但在第二种情况下,我根本没有发现。

Solutions Collecting From Web of "使用TCP Keep-Alive在写入阻塞的套接字上获取断开连接通知"

如果在传输所有数据之前拔除网络连接,则连接不会空闲,因此在某些实施中,保活定时器不会启动。 (请记住,keepalive不是TCP规范的一部分,因此如果有的话,它的实现是不一致的。)通常,由于指数退避和大量重试( tcp_retries2默认为15)的组合,在保持活动计时器启动之前,传输重试超时至多30分钟。

解决方法(如果有的话)取决于您正在使用的特定TCP实施。 一些较新版本的Linux(2011年1月4日发布的内核版本2.6.37)实现TCP_USER_TIMEOUT。 更多信息在这里 。

通常的建议是在应用层实现通信超时,而不是使用基于TCP的keepalive。 请参阅HTTP Keep-Alive 。

即使您已经为您的应用程序套接字设置了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) 

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

在最近的内核版本(从2.6.37开始)中,可以使用TCP_USER_TIMEOUT选项将会很好的工作。 这个选项可以用于单个插座。

我想谈谈几点…

1)根据这个文档 ,这里是在Linux中使用keepalive的必要条件:

Linux内置了对keepalive的支持。 您需要启用TCP / IP网络才能使用它。 您还需要procfs支持和sysctl支持才能在运行时配置内核参数。

涉及keepalive的程序使用三个用户驱动变量:

 tcp_keepalive_time 

>发送的最后一个数据包(简单的ACK不被视为数据)和第一个keepalive探测之间的间隔; 在连接被标记为需要保持连接之后,该计数器不再被使用

 tcp_keepalive_intvl 

>后续keepalive探测之间的时间间隔,不管在此期间交换了什么连接

 tcp_keepalive_probes 

>在考虑连接死亡并通知应用层之前发送的未确认探测的数量

请记住,即使在内核中配置Keepalive支持,也不是Linux中的默认行为。 程序必须使用setsockopt接口请求它们的套接字的存活控制。 实现Keepalive的程序相对较少,但可以按照本文稍后介绍的说明轻松地为其中大部分添加Keepalive支持。

尝试在当前系统中查看这些变量的当前值,以确保它们是正确的或有意义的。 大胆的亮点是我的,看来你是这样做的。

我假设这些变量的值是毫秒,但不知道,你仔细检查。

 tcp_keepalive_time 

我期望有一个值,意思是在'最后一个数据包发送后尽快',发送第一个探测'

 tcp_keepalive_intvl 

我猜这个变量的值应该小于TCP关闭连接所用的默认时间。

 tcp_keepalive_probes 

这可能是造成或破坏你的应用程序的“魔法价值” 如果未确认的探测器数量太高,可能是epoll_wait()永远不能退出的原因。

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

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

2)确保你没有在epoll_wait()的timeout参数中指定-1,因为它会导致epoll_wait()无限期地被阻塞。

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

timeout参数指定epoll_wait()将阻塞的最小毫秒数。 (此时间间隔将四舍五入到系统时钟粒度,内核调度延迟意味着阻塞时间间隔可能会超出一小部分时间。)指定超时值-1会导致epoll_wait()无限期阻塞,同时指定超时时间等于零导致epoll_wait()立即返回,即使没有事件可用。

从手册页http://linux.die.net/man/2/epoll_wait