背景
许多小伙伴做网络编程时,会自然而然的去做应用层的心跳检测机制,如果问为什么要做心跳检测,大部分人会说保活,及时发现连接失效等等。这篇文章会结合TCP协议原理来分析,使用应用层做心跳检测的原因和必要性。
没有应用层心跳检测时
在没有应用层心跳检测时,我们如何发现对端应用程序崩溃/对端主机崩溃/主机不可达等等异常呢?
假如网络正常
我们考虑异常时在发送数据包:
- 如果对端程序崩溃还未重启,会发送FIN,本端可以立即感知
- 如果对端 程序崩溃/主机崩溃 后已经重启,会发送RST,本端可以立即感知
- 对端主机崩溃一直未重启,我们发送的数据包将会TCP超时
- 对端主机不可达,我们发送的数据包将会TCP超时
TCP超时重传的默认机制是什么呢?
首先明确,超时重传的原理是在发送某一个数据以后就开启一个计时器,在一定时间内如果没有得到发送的数据报的ACK报文,那么就重新发送数据,直到发送成功为止。
关于超时重传的时间间隔和重传次数,下面这篇博客比较详细的讲解了这个知识点:TCP/IP重传超时--RTO
总结一下博客中的内容
- RTO有一个最小值,不同内核中是写死的,博客中提到的内核版本是200ms,就算RTT是0.5ms也不行的,会用200ms来发送第二次发送的数据包。
- TCP内核中可以修改两个参数
/proc/sys/net/ipv4/tcp_retries1 (integer; default: 3)
TCP尝试了3次(tcp_retries1默认3)重传后,还没有收到ACK的话,则后续每次重传都需要network layer先更新路由。
/proc/sys/net/ipv4/tcp_retries2 (integer; default: 15)
TCP默认最多做15次重传。根据RTO(retransmission timeout)不同,最后一次重传间隔大概是13到30分钟左右。如果15次重传都做完了,TCP/IP就会告诉应用层说:“搞不定了,包怎么都传不过去!”
如果没有应用层心跳检测,我们发送的数据包,最差需要依靠TCP超时来判断对端出现问题,这个超时时间是很长的,在现代的业务中几乎无法满足业务的需求。
我们考虑只接收对端数据包的过程:
- 如果对端程序崩溃,会发送FIN,本端可以立即感知
- 对端主机崩溃
- 如果关闭了Tcp的KeepAlive保活机制,那么没有任何感知;
- 如果开启了TCP keep_alive,会通过保活机制,发送几个检测包,要么超时,要么得到RST
- 对端主机不可达,和上面的主机崩溃同理
如果没有应用层心跳检测,我们在不发送,只接受的情况下,最差需要依靠Keep Alive机制的超时或RST来判断对端出现问题,超时时间为:2小时+75秒* 9次。这个时间在默认参数设置下也是很长的。另外,这三个参数也可以修改的:
net.ipv4.tcp_keepalive_time 对应2小时
net.ipv4.tcp_keepalive_intvl 对应75秒
net.ipv4.tcp_keepalve_probes 对应9次
假如网络异常
假如有网络异常,但是对方主机和程序都正常
发送数据包时:
- 通过本端TCP超时,发现网络异常
接收数据包时:
- 通过Keep_Alive机制来探测
有应用层心跳检测时
通过上面的分析可以得知,对于网络应用,无论是依赖TCP超时还是Keep Alive,在最差的情况下的超时时间都是不可接受的。所以我们需要在应用层做一个类似的心跳检测机制,保证可以在任何情况下都可以快速的检测出异常情况。
那么是否可以只发一次,在规定时间内没收到回复就判定连接异常呢?考虑下面的两种情况:
- 网络波动,但是对端主机和服务都正常
- 对端主机正忙于处理其他事务,响应慢了一些
对这两种情况来说,如果检测一次,发现超时就判定连接失效基本属于误伤,可以通过多发送几次心跳检测包来规避上面的误伤情况。
并且还需要考虑,在多次发送的心跳包,需要带序号,对端回复时,也要保证使用这个序号或者序号+1来匹配我们发出去的心跳包。如果编号对应不上,就不能算作此次检测成功了。