心跳包:告诉别人,我还活着

2024-06-03 10:32
文章标签 告诉 别人 心跳 活着

本文主要是介绍心跳包:告诉别人,我还活着,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

心跳包是什么?有什么用?

心跳一般是指客户端(也可以是服务器端)向对方每隔一段时间发送一个应用层的自定义指令,以确保连接的有效性。因为是固定间隔,同时是检测存活,就像人的心跳一样,顾名思义,称为心跳包。一般是用于长连接,对消息实时性要求比较高的服务中,比如IM服务,推送服务。

长连接有什么用?

在即时通讯领域和推送服务中,对消息的实时性和可用性要求非常高,建立长连接,可以有效节省DNS解释时间,TCP/IP三次握手时间,同时为了保证连接是可用的,不至于经常发了消息对方无法收到,必须要有一种机制检测连接的有效性。TCP是一个基于连接的协议,连接是由一个状态机进行维护,当连接建立成功后,双方都处于established ,除非我们进行主动调用,否则状态一直不会变化,即使中间路由已经崩溃,网线已经被剪断。TCP有一种KeepAlive机制,TCP层在定时时间发送相应的KeepAlive探针以确保连接的可用性,默认每7200秒发送一次,超过75秒没有返回就超时,超时后重试10次,虽然可以修改默认值,但仍然无法满足要求。尤其是考虑到一种特殊情况,TCP连接存活,但是主机不处于存活状态,比如CPU负载到100%,无法响应任何请求。这时候,就需要客户端主动切断连接,主动切换到其他备用机。

移动端面临的挑战

通常,我们一个家庭里面只接入一根网线,所有设备通过路由器共用一个出口IP,路由器就是一个NAT设备,NAT设备在IP封包流过设备的时候,自动修改源和目标地址,家用路由器甚至基于NAPT修改端口号,路由器内部会维护一个NAT映射表
比如内网里面的172.1.1.2:7777 对应外网221.22.2.1:8888等。我们的手机接入的蜂窝网络后,运营商就会给我们分配一个内网IP(类似10.2.2.3),由运营商的网管维护一个NAT的映射表,确保手机能接入互联网。大部分运营商会在手机一段时间没有数据通讯的时候,会把设备从NAT表中剔除,造成了连接中断,但是对TCP连接的双方是不可感知的,服务端就无法给客户端发送消息。像中国移动和中国联通的NAT超时时间是5分钟,国际上运营商普遍都是大于28分钟。

实现方案

合理间隔

心跳太短保证不了可靠性,太频繁会带来高耗电和大量的流量消耗,这在移动设备上面是不可接受的。最合理的解决方案是设定一个合理的间隔,一般可以根据程序状态进行调整,逐步拉长心跳间隔,5分钟,10分钟,甚至15分钟。服务端进行可靠性判断的时候也可以放宽标准,只有N次超时才被认为是连接已经断开。心跳的周期以最后一条指令为准,而非固定间隔。

自定义应用层实现协议

在DEMO中,双方约定一个协议,发送方先对管道写入一个8位的byte值,接收方只要一接收到数据,马上按照byte类型标准读取前8位,通过这一个字节的值来确定对方现在发过来的是什么类型的数据。为什么要选择byte呢?因为byte足够短,只占用一个字节,尽量减少数据传输量,可以通过一个字节表达256种情况。当然根据实际业务需求,选择int,long类型也是完全没问题的。
在这个例子中,我们约定byte的值是1的话,那么我们解释为心跳包,后面不再有数据,直接在屏幕中打印收到客户端的心跳包,byte的值是2的话,我们知道对方要发一个字符串过来,那么需要进一步处理,再次调用readUTF方法,读取一个UTF-8字符串

下面是一个用JAVA实现的心跳包DEMO,主要用了多线程和Socket

服务端代码

服务端建立一个类,采用同步多线程模式,主类负责接收socket请求,子线程Worker类负责处理业务逻辑

public class Server {public static void main(String[] args) {try {ServerSocket serverSocket = new ServerSocket(30000); //实例化ServerSocket,绑定监听本机的30000端口while(true){  Socket socket = serverSocket.accept();  //这个是阻塞方法,只有监听到客户端连接过来了,才会继续往下走。System.out.println(socket.getInetAddress().getHostName()+"连接到服务器...");//Worker线程启动代码Worker worker = new Worker(socket);new Thread(worker).start();}}catch (Exception e){System.out.println("主线程抛出异常");e.printStackTrace();}}
}

Worker线程

class Worker implements Runnable{private Socket socket;private InputStream in;private OutputStream out;private ObjectInputStream ois;private boolean flag = true;public Worker(Socket socket){try{this.socket = socket; //要获得一个从主线程传过来的客户端socket实例,每个客户端都不一样in = socket.getInputStream(); //从客户端实例中,获取输入流实例out = socket.getOutputStream(); //获取输出流实例ois = new ObjectInputStream(in); //实例化ObjectInputStream}catch (Exception e){System.out.println("worker构造函数抛出异常");e.printStackTrace();}}public void run(){try{while(flag) {//协议的第一位是数字,先读取第一位int type = ois.readByte();if(type == 1){//第一位是1的话,就直接当心跳包处理System.out.println("收到"+socket.getInetAddress().getHostAddress()+"发送过来的心跳包");}else if(type == 2){//第一位是2的话,我们可以知道,对方发过来的是UTF-8格式的String,所以可以调用readUTF方法继续读取System.out.println(socket.getInetAddress().getHostAddress()+"说:"+ois.readUTF());}}}catch (EOFException e){System.out.println("对方已关闭连接");flag = false;} catch (IOException ioe) {ioe.printStackTrace();}}
}

客户端代码

public class Client {private static final String host = "127.0.0.1";  //目标地址,这里是本机private static final int port = 30000; //目标端口public static void main(String[] args) {Socket socket = new Socket();try{socket.connect(new InetSocketAddress(host,port)); //建立socket连接OutputStream out = socket.getOutputStream(); //从socket中获取读取流的实例ObjectOutputStream oos = new ObjectOutputStream(out); //实例化ObjectOutputStream ,用于自定义的传输协议BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));  //用来读取键盘输入,用了缓冲类TimeStore timeStore = new TimeStore(); //建立一个类来存储数据最后发送时间new Thread(new SendHeartbeat(oos,timeStore)).start(); //启动心跳业务线程String line = new String();//下面几行代码用于获取用户输入while((line = bufferedReader.readLine()) != null){oos.writeByte(2); //要发送的是自定义协议的字符串,先写入一个2,告诉服务端,准备发送字符串数据oos.writeUTF(line); //写入一个UTF字符串到流中oos.flush();timeStore.setLastSendTime(System.currentTimeMillis()); //记录最后的写入时间到时间存储类}oos.close();}catch (IOException e){System.out.println("数据写入IO异常");}finally {try {socket.close();}catch (IOException e2){e2.printStackTrace();}}}
}

发送心跳的逻辑

心跳专门开一条线程来发送,这样不受主线程业务的堵塞代码影响

class SendHeartbeat implements Runnable{private ObjectOutputStream oos;private TimeStore timeStore;public SendHeartbeat(ObjectOutputStream oos,TimeStore timeStore){this.oos = oos;this.timeStore = timeStore;}public void run(){try{while(true){Thread.sleep(1000); //死循环,每秒启动一次//当上次发送时间是在10秒或之前,才发送心跳if((System.currentTimeMillis() - timeStore.getLastSendTime()) >= 10*1000){  //写入1,告诉服务端发送的是心跳包oos.writeByte(1);oos.flush();//记录时间timeStore.setLastSendTime(System.currentTimeMillis());}}}catch (Exception e){e.printStackTrace();}}
}

时间存储类,用于记录最后发送的时间

class TimeStore{private long lastSendTime;//多线程下读取需要加锁public synchronized long getLastSendTime() {return lastSendTime;}//同样,多线程下写入需要加锁public synchronized void setLastSendTime(long lastSendTime) {this.lastSendTime = lastSendTime; //把时间放到私有属性System.out.println("最后一次发包时间"+ new java.text.SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(new java.util.Date(lastSendTime))); //把发包时间打印到屏幕上}
}

效果

心跳包的挑战:信令风暴

2013年,中国移动曾把刀口指向了微信,正是因为心跳包可能会引起的信令风暴,微信占用了中移动60%的信令资源,但仅带来10%的移动数据流量。每次发送心跳包,都需要移动通信网络为用户分配资源,分配的过程体现在信令的发送和接收上。一次心跳包的发送过程,牵涉的信令多达几十条。后来微信对心跳间隔进行了优化才暂时平息了这场风波。微信采用的方案是当微信处于前台活跃状态时,使用固定心跳。微信进入后台(或者前台关屏)时,先用几次最小心跳维持长链接。然后进入后台自适应心跳计算。这样做的目的是尽量选择用户不活跃的时间段,来减少心跳计算可能产生的消息不及时收取影响。详看微信心跳包优化方案

这篇关于心跳包:告诉别人,我还活着的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1026730

相关文章

[情商-13]:语言的艺术:何为真实和真相,所谓真相,就是别人想让你知道的真相!洞察谎言与真相!

目录 前言: 一、说话的真实程度分级 二、说谎动机分级:善意谎言、中性谎言、恶意谎言 三、小心:所谓真相:只说对自己有利的真相 四、小心:所谓真相:就是别人想让你知道的真相 五、小心:所谓善解人意:就是别人只说你想要听到的话 前言: 何为真实和真相,所谓真相,就是别人想让你知道的真相!洞察谎言与真相! 人与人交流话语中,处处充满了不真实,完全真实的只是其中一小部分,这

数业智能心大陆告诉你如何培养孩子的批判性思维?

现今的教育体系自小学起便强调培养孩子的批判性思维,这种能力被视为在复杂世界中生存和发展的关键。在当今信息爆炸的时代,它能让我们在海量信息中辨别真伪、深入思考并做出明智决策。如今,如数业智能心大陆产出的AI 心理咨询平台的出现为培养孩子批判性思维提供了新可能,其通过互动引导孩子思考,助力孩子提升批判性思维能力。 什么是批判性思维呢? 批判性思维是一种思考方式,它能够使我们在接收信

当当图书福利券,满400减230,别说我没告诉你!

1024程序员节 当当网计算机图书 每满100减50! 满200减100! 满300-150! 机械工业出版社华章公司联合当当网特意为【大数据技术与架构】用户申请了一批可与满减叠加使用的“满200减30”的图书优惠码,优惠码使用后相当于: 400减230 !!!   优惠码:【YRMQMY】(注意区分大小写) 使用渠道:当当app和当当小程序 使用时间:10月22-10月31 本活动满减与礼券

mina 2 心跳包

接收到心跳后先解码,先不调用经过messageReceived()方法,先触发心跳接收发送类KeepAliveMessageFactoryImpl中的isRequest()方法,当判断是心跳时,就会发一个心跳,不再调用messageReceived(),当判断不是心跳时,回调messageReceived()方法,输出内容。//服务器import java.io.IOException;im

阿里十年架构师用一张图告诉你什么是系统架构师

阿里十年架构师用一张图告诉你什么是系统架构师 Java架构解析 2018-11-03 20:54:41 这张图从架构师的综合能力、岗位认识、岗位职责等方面,清楚的画出了作为一个架构的基本准则。人人都想成为架构师,可作为架构你达到了图上面的要求了吗?   系统架构师是个神奇的岗位。为什么这么说,在一个人数不多的小公司,你可能什么都需要做,身体力行,做总监兼架构师或者是主管/高级开发兼架构

B端:工作台页面放什么?不知道,这里告诉你10个常见内容。

工作台是B端系统的核心页面,也是最常用的页面,该页面的上通常放哪些内容了,是中说纷纭,本文把常放内容给大家列举下。 B端工作台页面是专门为企业用户设计的工作台,通常需要包含一些与企业工作相关的功能和信息。以下是一些常见的内容,可以考虑在B端工作台页面中展示: 数据概览: 展示企业的重要数据指标,比如销售额、订单量、客户数量等,让用户能够一目了然地了解企业的运营情况。 任务管理:

视频合并怎么操作?这篇文章告诉你

当你手头有多个片段,想要将它们巧妙地拼接在一起,形成一个完整的故事时,你会怎么做呢? 手动逐帧调整?这显然是个耗时且复杂的过程。幸好,现在有许多优秀的视频合并模板软件可以帮助你轻松实现这一目标。 今天,就让我们一起来看看五款值得尝试的视频合并模板软件,它们不仅能让视频编辑变得简单,还能让你的作品看起来专业又时尚。 一、【剪辑魔法师】 适用人群:适合追求高效剪辑和创意表达的视频制作爱好者

webSocket java.io.EOFException: null 增加心跳机制解决

最近发现webSocket连接,经常自动断开,看了晚上的一些文章,很多说是Nginx的问题,但是不想改Nginx因为怕影响其他系统,而且不一定有效,因此决定给webSocket加一个心跳机制: 1:先在服务端判断消息是不是心跳检测消息,是的话,原封不动将消息传给客户端即可: if("heartCheck".equals(jsonObject.getString("heartCheck

刚复制的vm workstation 虚机机,在别人机器上启动时报H:\centos-6.0-710\CentOS 64 位.vmdk”或它所依赖的某个快照磁盘 ” 原因: 未能锁定文件”

打不开磁盘“H:\centos-6.0-710\CentOS 64 位.vmdk”或它所依赖的某个快照磁盘 ” 原因: 未能锁定文件”      这主要是非正常关虚拟机造成的,具体原因如下:虚拟机为了防止有多虚拟机共用一个虚拟磁盘(就是后 缀为.vmdk那个文件)造成数据的丢失和性能的削弱,每次启动虚拟机时会给每个虚拟磁盘加一个磁盘锁(也就是后缀为.lck的那个文件夹)对虚拟磁盘文件 进行锁定保

面试官问:服务的心跳机制与断线重连,Netty底层是怎么实现的?懵了

点击上方“朱小厮的博客”,选择“设为星标” 后台回复"书",获取 后台回复“k8s”,可领取k8s资料 心跳机制 何为心跳 所谓心跳, 即在 TCP 长连接中, 客户端和服务器之间定期发送的一种特殊的数据包, 通知对方自己还在线, 以确保 TCP 连接的有效性. 注:心跳包还有另一个作用,经常被忽略,即:一个连接如果长时间不用,防火墙或者路由器就会断开该连接。 如何实现 核心Handler