为什么需要心跳机制?
考虑以下两种典型的即时通讯网络层问题模型:
1)情形一:
一个客户端连接服务器以后,如果长期没有和服务器有数据来往,则可能会被防火墙程序关闭连接。
但有时候我们并不想被关闭连接。
例如,对于一个即时通讯软件来说,如果服务器没有消息时,我们确实不会和服务器有任何数据交换,
但是,如果连接被关闭了,有新消息传来时,我们再也没法收到了,这就违背了“即时通讯”的设计要求。
解决:当服务器与客户端一定时间内没有有效业务数据来往时,我们只需要给对端发送心跳包即可实现保活。
2)情形二:
通常情况下,服务器与某个客户端一般不是位于同一网络的,之间可能经过多个路由器和交换机,
如果其中某个必经路由器或者交换器出现了故障,并且一段时间内没有恢复,则会导致这之间的链路不再通畅。
而此时服务器与客户端之间也没有数据进行交换,由于TCP连接是状态机,
对于这种情况,无论是客户端或者服务器都无法感知与对方的连接是否正常,这类连接我们一般称之为“死链”。
解决:
要解决“死链”问题,只要我们此时任意一端给对端发送一个心跳包即可检测链路是否正常。
如果一个连接长时间没有正常数据来往,也没有心跳包来往,就可以认为这个连接已经不存在,
为了节约服务器连接资源,我们可以通过关闭socket,回收连接资源。
应用层的心跳包机制设计
操作系统的TCP/IP协议栈其实提供了这个功能,即keep-alive选项。但默认时间是7200秒(2小时),一般不使用。
另外,由于keep-alive选项需要为每个连接中的socket开启,这不一定是必须的,可能会产生大量的无意义的带宽浪费,
且keep-alive选项不能与应用层很好地交互,因此一般实际的服务开发中,还是建议开发者在应用层设计自己的心跳包机制。
那么如何设计呢?
心跳包其实就是一个预先规定好格式的数据包,在程序中启动一个定时器,定时发送即可,这是最简单的实现思路。
但是,如果通信的两端有频繁的数据来往,这些数据包本身就可以起到保活的作用,为什么还要浪费流量去发送这些心跳包呢?
所以,对于用于保活的心跳包,我们的最佳做法是:
设置一个上次包的时间,每次收数据和发数据时,都更新一下这个包的时间,
而心跳检测计时器每次检测时,将这个包的时间与当前系统时间做一个对比,
如果时间间隔大于允许的最大时间间隔,则发送一次心跳包。
需要注意的是:
一般是客户端主动给服务端发送心跳包,服务端做心跳检测决定是否断开连接,而不是反过来。
对于心跳包,服务端的逻辑一般是在一定时间间隔内没有收到客户端心跳包时会主动断开连接。