TCP协议中有长链接和短链接之分。短链接环境下,数据交互完毕后,主动释放链接;html
双方创建交互的链接,可是并非一直存在数据交互,有些链接会在数据交互完毕后,主动释放链接,而有些不会,那么在长时间无数据交互的时间段内,交互双方都有可能出现掉电、死机、异常重启,仍是中间路由网络无端断开、NAT超时等各类意外。java
当这些意外发生以后,这些TCP链接并将来得及正常释放,那么,链接的另外一方并不知道对端的状况,它会一直维护这个链接,长时间的积累会致使很是多的半打开链接,形成端系统资源的消耗和浪费,为了解决这个问题,在传输层能够利用TCP的保活报文来实现,这就有了TCP的Keepalive(保活探测)机制。linux
在应用交互的过程当中,可能存在如下几种状况:数据库
利用保活探测功能,能够探知这种对端的意外状况,从而保证在乎外发生时,能够释放半打开的TCP、
链接。ubuntu
中间设备如防火墙等,会为通过它的数据报文创建相关的链接信息表,并未其设置一个超时时间的定时器,若是超出预约时间,某链接无任何报文交互的话,中间设备会将该链接信息从表中删除,在删除后,再有应用报文过来时,中间设备将丢弃该报文,从而致使应用出现异常,这个交互的过程大体以下图所示:服务器
这种状况在有防火墙的应用环境下很是常见,这会给某些长时间无数据交互可是又要长时间维持链接的应用(如数据库)带来很大的影响,为了解决这个问题,应用自己或TCP能够经过保活报文来维持中间设备中该链接的信息,(也能够在中间设备上开启长链接属性或调高链接表的释放时间来解决。网络
常见应用故障场景:socket
某财务应用,在客户端须要填写大量的表单数据,在客户端与服务器端创建TCP链接后,客户端终端使用者将花费几分钟甚至几十分钟填写表单相关信息,终端使用者终于填好表单所需信息后,点击“提交”按钮,结果,这个时候因为中间设备早已经将这个TCP链接从链接表中删除了,其将直接丢弃这个报文或者给客户端发送RST报文,应用故障产生,这将致使客户端终端使用者全部的工做将须要从新来过,给使用者带来极大的不便和损失。 tcp
TCP保活的交互过程大体以下图所示:
ide
TCP保活可能带来的问题:
当链接一端在发送保活探测报文时,中间网络正好因为各类异常(如链路中断、中间设备重启等)而没法将保活探测报文正确转发至对端时,可能会致使探测的一方释放原本正常的链接,可是这种可能状况发生的几率较小,
另外,通常也能够增长保活探测报文发生的次数来减小这种状况发生的几率和影响。
下面协议解读,基于 RFC1122#TCP Keep-Alives (注意这是协议的解读站在协议的角度)
TCP Keepalive不是TCP规范的一部分,有三点须要注意:
如下环境是在Linux服务器上进行,应用程序若想使用须要设置SO_KEEPALIVE套接口选项才可以生效。
发送频率tcp_keepalive_intvl乘以发送次数tcp_keepalive_probes,就获得了从开始探测到放弃探测肯定链接断开的时间;
若设置,服务器在客户端链接空闲的时候,每90秒发送一次保活探测包到客户端,若没有及时收到客户端的TCP Keepalive ACK确认,将继续等待15秒*2=30秒。总之能够在90s+30s=120秒(两分钟)时间内可检测到链接失效与否。
如下改动,须要写入到/etc/sysctl.conf文件:
net.ipv4.tcp_keepalive_time=90 net.ipv4.tcp_keepalive_intvl=15 net.ipv4.tcp_keepalive_probes=2
保存退出,而后执行sysctl -p
生效
可经过 sysctl -a | grep keepalive
命令检测一下是否已经生效。
针对已经设置SO_KEEPALIVE的套接字,应用程序不用重启,内核直接生效。
只须要在服务器端一方设置便可,客户端彻底不用设置,好比基于netty 4服务器程序:
ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) // 心跳监测 .childOption(ChannelOption.SO_KEEPALIVE, true) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast( new EchoServerHandler()); } }); // Start the server. ChannelFuture f = b.bind(port).sync(); // Wait until the server socket is closed. f.channel().closeFuture().sync();
Java程序只能作到设置SO_KEEPALIVE选项,至于TCP_KEEPCNT,TCP_KEEPIDLE,TCP_KEEPINTVL等参数配置,只能依赖于sysctl配置,系统进行读取。
其余语言怎么使用:连接
默认状况下使用keepalive周期为2个小时,如不选择更改属于误用范畴,形成资源浪费:内核会为每个链接都打开一个保活计时器,N个链接会打开N个保活计时器。
优点很明显:
关闭TCP的keepalive,彻底使用业务层面心跳保活机制 彻底应用掌管心跳,灵活和可控,好比每个链接心跳周期的可根据须要减小或延长
业务心跳 + TCP keepalive一块儿使用,互相做为补充,但TCP保活探测周期和应用的心跳周期要协调,以互补方可,不可以差距过大,不然将达不到设想的效果。
朋友的公司所作IM平台业务心跳2-5分钟智能调整 + tcp keepalive 300秒,组合协做,听说效果也不错。
虽说没有固定的模式可遵循,那么有如下原则能够参考:
咱们知道TCP链接关闭时,须要链接的两端中的某一方发起关闭动做,若是某一方忽然断电,另一端是没法知道的。tcp的keep_alive就是用以检测异常的一种机制。
有三个参数:
若是是Linux操做系统,这三个值分别为
huangcheng@ubuntu:~$ cat /proc/sys/net/ipv4/tcp_keepalive_time 7200 huangcheng@ubuntu:~$ cat /proc/sys/net/ipv4/tcp_keepalive_intvl 75 huangcheng@ubuntu:~$ cat /proc/sys/net/ipv4/tcp_keepalive_probes 9
也就意味着每隔7200s(两个小时)发起一次keepalive的报文,若是没有回应,75秒后进行重试,最多重试9次即认为链接关闭。
这三个选项分别对应TCP_KEEPIDLE、TCP_KEEPINTL和TCP_KEEPCNT的选项值,经过setsockopt进行设置。
可是,tcp本身的keepalive有这样的一个bug:
**
正常状况下,链接的另外一端主动调用colse关闭链接,tcp会通知,咱们知道了该链接已经关闭。
可是若是tcp链接的另外一端忽然掉线,或者重启断电,这个时候咱们并不知道网络已经关闭。
而此时,若是有发送数据失败,tcp会自动进行重传。
重传包的优先级高于keepalive,那就意味着,咱们的keepalive老是不能发送出去。 而此时,咱们也并不知道该链接已经出错而中断。在较长时间的重传失败以后,咱们才会知道。
为了不这种状况发生,咱们要在tcp上层,自行控制。
对于此消息,记录发送时间和收到回应的时间。若是长时间没有回应,就多是网络中断。若是长时间没有发送,就是说,长时间没有进行通讯,能够自行发一个包,用于keepalive,以保持该链接的存在。
按照例子的值在一端的socket上开启keep alive,而后阻塞在一个recv或者不停的send,这个时候拔了网线,测试从拔掉网线到recv/send返回失败的时间。
在linux kernel里头的测试发现,对于阻塞型的socket,当recv的时候,若是没有设置keep alive,即便网线拔掉或者ifdown,recv很长时间不会返回,最长达17分钟,虽然这个时间比linux的默认超时时间短了不少。
可是若是设置了keep alive,基本都在keepalive_time +keepalive_probeskeepalive_intvl =33秒内返回错误。*
可是对于循环不停send的socket,当拔掉网线后,会持续一段时间send返回成功(0~10秒左右,取决 于发送数据的量),而后send阻塞,由于协议层的buffer满了,在等待buffer空闲,大概90秒左右后才会返回错误。
由此看来,send的时候,keep alive彷佛没有起到做用,这个缘由是由于重传机制。能够经过给send以前设置timer来解决的。