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

时间:2013-05-01 14:29:54

标签: linux tcp epoll 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的通信时,在第一种情况下(读取)我会在几秒钟内获得一次ack请求。但在第二种情况下,我根本没有发现它们。

3 个答案:

答案 0 :(得分:13)

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

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

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

答案 1 :(得分:1)

即使您已经为应用程序套接字设置了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选项将运行良好。此选项可用于单个插槽。

答案 2 :(得分:0)

我想谈谈几点......

1)根据this document,以下是在Linux中使用keepalive的必要条件:

  

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

     

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

tcp_keepalive_time 
     

>发送的最后一个数据包之间的间隔(简单的ACK不是   考虑数据)和第一个keepalive探测器;连接后   标记为需要keepalive,此计数器不再使用

tcp_keepalive_intvl 
     

>无论如何,后续的keepalive探测之间的间隔   在此期间连接交换了什么

tcp_keepalive_probes 
     

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

     

请记住,即使在内核中配置了keepalive支持,也是如此   不是Linux中的默认行为。 程序必须请求keepalive   使用setsockopt接口控制其套接字。有   实现keepalive的程序相对较少,但您可以轻松添加   按照说明为大多数人提供keepalive支持   本文稍后会解释。

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

我假设这些变量的值以毫秒为单位,但不确定,请仔细检查。

tcp_keepalive_time

我希望在发送最后一个数据包之后“发送第一个探测”

,这意味着“ASAP”
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()的超时参数中指定-1,因为它会导致epoll_wait()无限期阻止。

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

timeout参数指定最小毫秒数   epoll_wait()将阻止。 (这个间隔将四舍五入到   系统时钟粒度和内核调度延迟意味着   阻塞间隔可能会少量超出。)指定超时   -1表示epoll_wait()无限期阻塞,同时指定a   timeout等于零会导致epoll_wait()立即返回   如果没有可用的事件。

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