博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

tcp keepalive tcp心跳 应用层心跳

Posted on 2014-10-23 14:09  bw_0927  阅读(1721)  评论(0)    收藏  举报

http://blog.csdn.net/aa2650/article/details/17027845

http://www.felix021.com/blog/read.php?2076

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

 

1. TCP保活的必要性

1) 很多防火墙等对于空闲socket自动关闭

2) 对于非正常断开, 服务器并不能检测到. 为了回收资源, 必须提供一种检测机制.


2. 导致TCP断连的因素

如果网络正常, socket也通过close操作来进行优雅的关闭, 那么一切完美. 可是有很多情况, 比如网线故障, 客户端一侧突然断电或者崩溃等等, 这些情况server并不能正常检测到连接的断开. 


3. 保活的两种方式:

1) 应用层面的心跳机制

自定义心跳消息头. 一般客户端主动发送, 服务器接收后进行回应(也可以不回应). 这里不进行详述.

PS: 有人从软件的功能角度列出第三种方式, 就是通过第三方软件来进行探测, 确定连接的有效性. 这种方式局限性很大, 而且不属于软件内部的功能实现. 不进行讨论.

2) TCP协议自带的保活功能

打开keep-alive功能即可. 具体属性也可以通过API设定.


4. 两种方式的优劣性

TCP协议自带的保活功能, 使用起来简单, 减少了应用层代码的复杂度. 推测也会更节省流量, 因为一般来说应用层的数据传输到协议层时都会被加上额外的包头包尾. 由TCP协议提供的检活, 其发的探测包, 理论上实现的会更精妙(用更少的字节完成更多的目标), 耗费更少的流量.

由应用自己实现的应用层的心跳, 为心跳消息额外定义一个消息类型就可以了. 就是应用正常的消息包, 只是这个包特殊点, 专门用来检活而已, 通常比较小, 可能只有消息头就可以了, 除非需要额外的信息. 

应用层心跳的好处我个人的理解有两点: 

一是比较灵活, 因为协议层的心跳只能提供最纯粹的检活功能, 但是应用层自己可以随意控制, 包括协议可能提供的是秒级的, 但是你想做成毫秒级的都任意(虽然实际几乎不会有这种时间级别的心跳), 包里还甚至可以携带额外的信息, 这些都是灵活之处.

二是通用, 应用层的心跳不依赖协议. 如果有一天不用TCP要改为UDP了, 协议层不提供心跳机制了, 但是你应用层的心跳依旧是通用的, 可能只需要做少许改动就可以继续使用.

应用层心跳的不好的地方也很显而易见, 增加开发工作量, 由于应用特定的网络框架, 还可能很增加代码结构的复杂度. 再就是根据上面的推测, 应用层心跳的流量消耗还是更大的, 毕竟这本质上还是个普通的数据包.


5. 到底选用那种心跳方式?

优劣点第4节已经进行了阐述, 因此如果能确定你们更换协议的可能性非常小, 同时只是需要检活的功能, 那么用协议自带的就绝对OK了, 使用简单而且高效. 有些自负的人总喜欢用自己搞的, 来代替成熟协议自带的东西, 代替系统内核提供的东西, 其实往往你应用层实现的东西, 都是更拙劣的. 网上看了一些关于协议的Keep-alive不靠谱的说法, 也都比较空想和想当然, 都没有拿出任何事实论据或实验数据. 这点大家有见解, 欢迎交流哈~


6. 类Unix平台如何使用Keep-alive

keepalive默认是关闭的, 因为虽然流量极小, 毕竟是开销. 因此需要用户手动开启. 有两种方式开启.

1) 在代码里针对每个socket进行单独设定, 使用起来灵活.

除了keepAlive 开关, 还有keepIdle, keepInterval, keepCount 3个属性, 使用简单, 如下:

 

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. int keepAlive = 1;   // 开启keepalive属性. 缺省值: 0(关闭)  
  2. int keepIdle = 60;   // 如果在60秒内没有任何数据交互,则进行探测. 缺省值:7200(s)  
  3. int keepInterval = 5;   // 探测时发探测包的时间间隔为5秒. 缺省值:75(s)  
  4. int keepCount = 2;   // 探测重试的次数. 全部超时则认定连接失效..缺省值:9(次)  
  5. setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive));  
  6. setsockopt(s, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));  
  7. setsockopt(s, SOL_TCP, TCP_KEEPINTVL, (void*)&keepInterval, sizeof(keepInterval));  
  8. setsockopt(s, SOL_TCP, TCP_KEEPCNT, (void*)&keepCount, sizeof(keepCount));  

 

使用时需要#include <netinet/tcp.h>, 否则SOL_TCP和TCP_KEEPIDLE等3个宏找不到.

ps: 忍不住吐槽一下, 网上大量毫不负责的转载, 千篇一律的搜索结果, 很多人根本都没进行过任何验证吧. 为了找这么个头文件都费了不小的事. 大多数帖子里的说的都是不可用的.

2) 修改配置文件, 对整个系统所有的socket有效.

我们可以用cat命令查看到系统中这几个默认的值.

#cat /proc/sys/net/ipv4/tcp_keepalive_time  7200  

#cat /proc/sys/net/ipv4/tcp_keepalive_intvl  75  

#cat /proc/sys/net/ipv4/tcp_keepalive_probes  9

修改它们:

#echo 60 > /proc/sys/net/ipv4/tcp_keepalive_time  

#echo 5 > /proc/sys/net/ipv4/tcp_keepalive_intvl  

#echo 3 > /proc/sys/net/ipv4/tcp_keepalive_probes


链接推荐:

闲说HeartBeat心跳包和TCP协议的KeepAlive机制

TCP Keepalive HOWTO

 
 
很多应用层协议都有HeartBeat机制,通常是客户端每隔一小段时间向服务器发送一个数据包,通知服务器自己仍然在线,并传输一些可能必要的数据。使用心跳包的典型协议是IM,比如QQ/MSN/飞信等协议。

学过TCP/IP的同学应该都知道,传输层的两个主要协议是UDP和TCP,其中UDP是无连接的、面向packet的,而TCP协议是有连接、面向流的协议。

所以非常容易理解,使用UDP协议的客户端(例如早期的“OICQ”,听说OICQ.com这两天被抢注了来着,好古老的回忆)需要定时向服务器发送心跳包,告诉服务器自己在线。

然而,MSN和现在的QQ往往使用的是TCP连接了,尽管TCP/IP底层提供了可选的KeepAlive(ACK-ACK包)机制,但是它们也还是实现了更高层的心跳包。似乎既浪费流量又浪费CPU,有点莫名其妙。

具 体查了下,TCP的KeepAlive机制是这样的,首先它貌似默认是不打开的,要用setsockopt将 SOL_SOCKET.SO_KEEPALIVE设置为1才是打开,并且可以设置三个参数 tcp_keepalive_time/tcp_keepalive_probes/tcp_keepalive_intvl,分别表示连接闲置多久开始 发keepalive的ack包、发几个ack包不回复才当对方死了、两个ack包之间间隔多长,在我测试的Ubuntu Server 10.04下面默认值是7200秒(2个小时,要不要这么蛋疼啊!)、9次、75秒。于是连接就了有一个超时时间窗口,如果连接之间没有通信,这个时间窗 口会逐渐减小,当它减小到零的时候,TCP协议会向对方发一个带有ACK标志的空数据包(KeepAlive探针),对方在收到ACK包以后,如果连接一 切正常,应该回复一个ACK;如果连接出现错误了(例如对方重启了,连接状态丢失),则应当回复一个RST;如果对方没有回复,服务器每隔intvl的时 间再发ACK,如果连续probes个包都被无视了,说明连接被断开了。

这里有一篇非常详细的介绍文章: http://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO ,包括了KeepAlive的介绍、相关内核参数、C编程接口、如何为现有应用(可以或者不可以修改源码的)启用KeepAlive机制,很值得详读。

这 篇文章的2.4节说的是“Preventing disconnection due to network inactivity”,阻止因网络连接不活跃(长时间没有数据包)而导致的连接中断,说的是,很多网络设备,尤其是NAT路由器,由于其硬件的限制(例 如内存、CPU处理能力),无法保持其上的所有连接,因此在必要的时候,会在连接池中选择一些不活跃的连接踢掉。典型做法是LRU,把最久没有数据的连接 给T掉。通过使用TCP的KeepAlive机制(修改那个time参数),可以让连接每隔一小段时间就产生一些ack包,以降低被T掉的风险,当然,这 样的代价是额外的网络和CPU负担。

前面说到,许多IM协议实现了自己的心跳机制,而不是直接依赖于底层的机制,不知道真正的原因是什么。

就 我看来,一些简单的协议,直接使用底层机制就可以了,对上层完全透明,降低了开发难度,不用管理连接对应的状态。而那些自己实现心跳机制的协议,应该是期 望通过发送心跳包的同时来传输一些数据,这样服务端可以获知更多的状态。例如某些客户端很喜欢收集用户的信息……反正是要发个包,不如再塞点数据,否则包 头又浪费了……

大概就是这样吧,如果有大牛知道真正的原因,还望不吝赐教。


@2012-04-21

p.s. 通过咨询某个做过IM的同事,参考答案应该是,自己实现的心跳机制通用,可以无视底层的UDP或TCP协议。如果只是用TCP协议的话,那么直接使用KeepAlive机制就足够了。

--

 


 

https://cloud.tencent.com/developer/article/1411975

在写TCP服务程序时,除了要处理SIGPIPE外,还要有客户端连接检测机制,用于及时发现崩溃的客户端连接。一般来说,有两种检测方式:1. 在应用层,由业务程序自己检测;2. 使用TCP的KeepAlive机制。

使用第一种方式,意味着要在应用层自己实现一个ping-pong逻辑和协议,并支持设置空闲时长,重试次数,重试间隔等。这无疑会增加一定的代码量,好处则是可以自己控制逻辑,同时不用学习内核的实现:)

但是如果没有特殊的需求,我更倾向于第二种方式。如非必要,不要引入额外的逻辑。更何况既然TCP本身已经支持KeepAlive,为什么还要自己实现一套,多此一举呢?代码写的越多,越可能引入Bug:D

本文将对TCP的KeepAlive的使用和原理做比较详细的分析。先看如何使用TCP KeepAlive来检测“失联”的TCP连接。完整的测试代码位于:https://github.com/gfreewind/LinuxDetails/blob/master/networks/6.tcp_keepalive_reporter/tcp_keepalive_report.c。

其中SO_KEEPALIVE用于打开或者关闭KeepAlive功能,

TCP_KEEPIDLE用于设置空闲时间——即有多久没有发送报文就进行探测,

TCP_KEEPCNT用于设置KeepAlive的尝试次数,

TCP_KEEPINTVL用于设置重试KeepAlive的报文间隔。

这里容易搞混的是TCP_KEEPIDLE和TCP_KEEPINTVL,前者是需要进行KeepAlive探测的空闲时间,而后者是在某次KeepAlive探测失败,再次重试的间隔时间。

对于上面的程序来说,当该TCP连接有5秒没有进行数据传输时,就会发送KeepAlive探测报文。当探测报文失败时,会隔2秒再次发送探测报文,3次探测失败就判断连接失败。

通过测试程序,我们可以使用tcpdump抓包,来分析KeepAlive。

 

 

前三个报文是TCP的三次握手,连接成功后,没有任何报文发送。间隔5秒后,发送KeepAlive,即第4个报文。第5个报文为KeepAlive ACK。再间隔5秒后,再次发送KeepAlive探测报文,即第6个报文。

(请忽略报文的黑颜色,因为这个测试是本机发给本机,所以TCP校验和是不正确的——没有真正通过网卡)

为了测试KeepAlive检测报文失败的情况,在连接成功之后,我使用iptables创建一条规则,丢弃发往port 34567端口的数据报文。这时抓包结果如下:

同上,前三个报文完成TCP三次握手,间隔5秒后发送KeepAlive探测报文,但由于没有收到ACK,所以每间隔2秒再次发送KeepAlive,重试3次后,判定连接失败,在11秒时(应该发送第4个KeepAlive时)发送RST给对端,中断连接。
 
 

那么当KeepAlive机制判断连接崩溃时,应用层如何得到通知呢?

当连接正常关闭时,应用层可以得到可读事件通知,并且进行read操作时,返回结果为0——这也是服务端判断客户端关闭连接的方法。

就此推测,KeepAlive机制判断连接崩溃时,其行为应该与正常关闭类似。在测试代码中,分别使用了select和epoll来进行io事件测试,其输出如下:

 

 

 

根据测试,无论是使用select还是epoll,当KeepAlive中止连接时,应用层都可以得到可读事件通知,并且read结果为0。上面的输出,还有一个sock err is 110的结果。这是另外一种判断机制:

 

 

当KeepAlive中断连接时,其会设置socket错误,应用层通过getsockopt即SO_ERR可以获得该错误。

 

 

 

通过测试程序,我们只是学到了KeepAlive的使用方法。接下来就要进入内核对KeepAlive一探究竟。

tcp_keepalive_timer为KeepAlive定时器的回调函数。在这个函数中

当探测超时,就会调用tcp_send_active_reset向对端发送RST报文,中止连接,然后调用tcp_write_err。

设置sk->sk_err等于ETIMEOUT,其值就是测试程序打印的110。而sk_error_report指向的是sock_def_error_report。

其会唤醒等待在当前套接字的进程,且其IO事件是POLLERR。

 

而在使用epoll_ctl添加监听fd时,内核会自动把EPOLLERR和EPOLLHUP加到监听事件中。这就是为什么epoll可以得到通知。

至此,我们已经从实现和使用上,完成了对TCP KeepAlive机制的探索。