Socket心跳检测的思考

发布于 2021-11-06  679 次阅读


在完成自己的Bio项目时,考虑到了心跳检测,有必要整理相关知识,特此记录

什么是掉线?

一. 从程序的角度看待TCP掉线

       TCP掉线的原因多种多样、不一而足,比如,客人的电脑突然断电、OS崩溃、路由器重启、网线接触不良、因为P2P下载软件而导致网络资源短缺、Internet网络的不稳定等等,但是从程序的角度来说,我们可以总结为两种情况:程序能立即感知的掉线和程序不能立即感知的掉线。

1. 程序能立即感知的掉线

       也就是说客户端一掉线,服务器端的某个读写对应的TCP连接的线程就会抛出异常,这种情况相对容易处理。

2. 程序不能立即感知的掉线(https://blog.csdn.net/kkkkkxiaofei/article/details/12966407?spm=1001.2101.3001.6650.5&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-5.essearch_pc_relevant&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-5.essearch_pc_relevant

  我们都知道,TCP连接的建立,需要经过三次握手;而TCP连接的断开,需要经过四次挥手。掉线通常没什么大不了的,掉就掉了呗,只要四次挥手顺利完成后,服务器和客户端分别做一些善后处理就可以。

       麻烦的事情在于,连接在没有机会完成4次挥手时已经断开了(比如当客人的电脑系统死机,或客人电脑与服务器之间的某处物理网线断开),而服务端以为客户端还正常在线,而客户端也自以为还正常在线。这种程序对现实状态的错误判断有可能引发诸多悲剧。比如,在此情况下,客户端发一个指令给服务器,服务器因为没有收到而一直处于等待指令的状态;而客户端了,以为服务器已经收到了,也就一直处于等待服务端回复的状态。如果程序的其它部分需要依据当前的状态来做后续的操作,那就可能会出问题,因为程序对当前连接状态的判断是错误的。

       毫无疑问,这种对连接状态错误的判断所持续的时间越久,带来可能的危害就越大。当然,如果我们不做任何额外的处理措施,服务器到最后也能感受到客户端的掉线,但是,这个时间可能已经过去了几分钟甚至几十分钟。对于大多数应用来说,这是不可忍受的。 所以,针对这种不能立即感知掉线的情况,我们要做的补救措施,就是帮助程序尽快地获知tcp连接已断开的信息。

      首先,我们可以在Socket上通过Socket.IOControl方法设置KeepAliveValues,来控制底层TCP的保活机制,比如,设定2秒钟检测一次,超过10秒检测失败时抛出异常。  

       据我们的经验,这种设定可以解决一部分问题,但是仍然会有一些连接在断开后,远远超过10秒才被感知掉。所以,这个补救措施还是远远不够的。我们还需要在应用层加入我们自己的TCP连接状态检测机制,这种机制就是通常所说的“心跳”。

 

使用socket.isConnected()或者socket.isClosed()等方法来判断Socket的实时状态的问题

https://blog.csdn.net/cshichao/article/details/8900446

很多人会说用socket.isConnected()或者socket.isClosed()等方法来判断就行了,但事实上这些方法都是访问socket在内存驻留的状态,当socket和服务器端建立链接后,即使socket链接断掉了,调用上面的方法返回的仍然是链接时的状态,而不是socket的实时链接状态

什么是心跳检测?(https://www.cnblogs.com/havenshen/p/3850167.html)

简单来说就是 客户端和服务器端建立连接后,有一段时间没有相互发送消息,这个时候为了知道对方是否还存活,发送心跳数据,看能否发送成功或对方有无回应,再根据结果执行其他的逻辑(例如重新建立连接)

通过什么实现心跳检测?

底层实现是:keepAlive对TCP连接进行设置

keep alive三个参数:

sk->keepalive_probes:探测次数
sk->keepalive_time 探测的超时
sk->keepalive_intvl 探测间隔

对 于一个已经建立的tcp连接。如果在keepalive_time时间内双方没有任何的数据包传输,则开启keepalive功能的一端将发送 keepalive数据包,若没有收到应答,则每隔keepalive_intvl时间再发送该数据包,发送keepalive_probes次。一直没有 收到应答,则发送rst包关闭连接。若收到应答,则将计时器清零。

 

那如何开启keepalive功能呢?

重点:通过Socket设置keepalive参数,开启keepalive方式来实现;

欸 不是说tcp的keepalive吗?怎么又提到Socket了?(简单来说就是Socket的数据传输方式可以是UDP也可以是TCP)

这就要说到Socket的本质:我们知道网络中进程间通信是利用三元组【ip地址,协议,端口】来实现的,而Socket就是一种技术 或者称之为 网络间通信的中间件工具,并且基本上现在所有的应用程序都采用Socket来进行通信。而Socket这种通信的数据传输方式有两种:

    a、SOCK_STREAM(TCP):表示面向连接的数据传输方式。数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送,但效率相对较慢。常见的 http 协议就使用 SOCK_STREAM 传输数据,因为要确保数据的正确性,否则网页不能正常解析。
  b、SOCK_DGRAM(UDP):表示无连接的数据传输方式。计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。因为 SOCK_DGRAM 所做的校验工作少,所以效率比 SOCK_STREAM 高。
也就是说Socket可以通过TCP的数据传输方式实现,也可以通过UDP的数据传输方式实现。前面说了目前所有的应用程序都采用Socket来通信,那么当然,许许多多的语言也支持Socket的使用,接下来就看看在Java中Socket的使用吧~
TCP:
UDP:
因此 回到前面,Socket实现心跳,直接在客户端调用Socket.setKeepalive(true)方法即可(Java语言中)。

那么对于Java中的Socket的Keepalive 我们如何理解呢?

ava socket编程中有个keepalive选项,看到这个选项经常会误解为长连接,不设置则为短连接,实则不然。

socket连接建立之后,只要双方均未主动关闭连接,那这个连接就是会一直保持的,就是持久的连接。keepalive只是为了防止连接的双方发生意外而通知不到对方,导致一方还持有连接,占用资源。

其实这个选项的意思是TCP连接空闲时是否需要向对方发送探测包,实际上是依赖于底层的TCP模块实现的,java中只能设置是否开启,不能设置其详细参数,只能依赖于系统配置。

首先看看源码里面是怎么说的

源码的意思是,如果这个连接上双方任意方向在2小时之内没有发送过数据,那么tcp会自动发送一个探测探测包给对方,这种探测包对方是必须回应的,回应结果有三种:

1.正常ack,继续保持连接;

2.对方响应rst信号,双方重新连接。

3.对方无响应。

这里说的两小时,其实是依赖于系统配置,在linux系统中(windows在注册表中,可以自行查询资料),tcp的keepalive参数

net.ipv4.tcp_keepalive_intvl = 75 (发送探测包的周期,前提是当前连接一直没有数据交互,才会以该频率进行发送探测包,如果中途有数据交互,则会重新计时tcp_keepalive_time,到达规定时间没有数据交互,才会重新以该频率发送探测包)
net.ipv4.tcp_keepalive_probes = 9  (探测失败的重试次数,发送探测包达次数限制对方依旧没有回应,则关闭自己这端的连接)
net.ipv4.tcp_keepalive_time = 7200 (空闲多长时间,则发送探测包)

为了能验证所说的,我们来进行测试一下,本人测试环境是客户端在本地windows上,服务端是在远程linux上,主要测试服务器端向客户端发送探测包(客户端向服务端发送是一样的原理)。

验证链接:https://www.cnblogs.com/xiao-tao/p/9718017.html

 

为什么需要自己在应用层实现Keepalive?

上述只是从TCP底层角度来实现了Keepalive心跳功能,但实际上它是存在Bug的,即:正常情况下,连接的另一端主动调用colse关闭连接,tcp会通知,我们知道了该连接已经关闭。但是如果tcp连接的另一端突然掉线,或者重启断电,这个时候我们并不知道网络已经关闭。而此时,如果有发送数据失败,tcp会自动进行重传。重传包的优先级高于keepalive,那就意味着,我们的keepalive总是不能发送出去。 而此时,我们也并不知道该连接已经出错而中断。在较长时间的重传失败之后,我们才会知道。

 

为了避免这种情况发生,我们要在tcp上层,自行控制。对于此消息,记录发送时间和收到回应的时间。如果长时间没有回应,就可能是网络中断。如果长时间没有发送,就是说,长时间没有进行通信,可以自行发一个包,用于keepalive,以保持该连接的存在。


她喜欢所以就做咯