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

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

相关文章

导入别人的net文件报红问题sdk

1. 使用cmd命令 dotnet --info 查看自己使用的SDK版本 2.直接找到项目中的 global.json 文件,右键打开,直接修改版本为本机的SDK版本,就可以用了

网站优化:搜索引擎告诉你应该怎么做SEO

我们研究SEO的,都必须学会观察。还得长时间的观察,才能发现一些搜索引擎的蛛丝马迹。才能总结出搜索引擎的一些特征。然后我们才能利用这种特征来指导我们的实际操作。这就是一个流程,这就是从理论到实践的一个步骤。 我最近一直在观察SEO这个关键词的排名。发现百度站长平台的排名排到了百度的第五的位置,见下图: 难道这真是因为,它是百度自身的产品,而百度懂得自己算法,才会出现这种结果的吗?我

导入别人的net文件报红问题

1. 使用cmd命令 dotnet --info 查看自己使用的SDK版本 2.直接找到项目中的 global.json 文件,右键打开,直接修改版本为本机的SDK版本,就可以用了

CPU飙升100%怎么办?字节跳动面试官告诉你答案!

小北说在前面 CPU占用率突然飙升是技术人员常遇到的一个棘手问题,它是一个与具体技术无关的普遍挑战。 这个问题可以很简单,也可以相当复杂。 有时候,只是一个死循环在作祟。 有时候,是死锁导致的。 有时候,代码中有不必要的同步块。 有时候,是大量计算密集型任务在运行。 有时候,是线程数过多引起的。 有时候,是频繁的上下文切换。 有时候,是内存不足的问题。 有时候,是频繁的垃圾回收。 有时候,

新手如何开始学深度学习?别着急,看看别人走过的路先

点击上方“AI公园”,关注公众号,选择加“星标“或“置顶” 作者:Arkar Min Aung 编译:ronghuaiyang 前戏 新手如何开始学深度学习,这个问题很难回答,每个人似乎都有自己的一套方法,在开始之前,先看看别人是怎么做的,也许会对自己有所启发。 刚开始学深度学习的时候,一定要像婴儿学步一样,慢慢的小步走。 很多人问我如何开始机器学习和深度学习。在这里,我整理了一个我使用过

香港优才,申请材料信息差!中介不会告诉你~

这两年diy申请香港优才身份的人非常多,获批都过万~但是被拒的数量更多~ 我就是打破信息差,快人一步申请,获批也早一点。就我经验来说,材料非常重要,因为申请佐证材料不足被拒的人非常多。 香港优才材料·常见注意事项 1️⃣基础证件:申请人+受养人的身份证、港澳通行证等 2️⃣尽量每份工作都有详细的证明材料(工作证明/离职证明、合同、社保记录等) 3️⃣关于工作:国际工作加分:港澳台工作

Lamp兄弟连第五十期开篇宣言 “要么赶紧死,要么精彩的活着”

要么赶紧死,要么精彩的活着” 听到这句话的人,大部分人应该是不会陌生的,这句话出至中国达人秀第一季冠军刘伟的经典语录,话很直白,也很明了,从他那不放弃自我的求生,不放弃自我梦想的追寻,最终造就了奇迹的诞生,而他也因此得到了世界上独一无二的,既优雅又传奇的名字“断臂钢琴王子”。 2010年7月 年仅23岁的刘伟 参加东方卫视《中国达人秀》 同年10月获得冠

无线领夹麦克风哪个牌子好?什么牌子的麦克风好?本期文章告诉你

​在音频设备日益多样化的今天,无线领夹麦克风以其独特的优势脱颖而出。它设计精巧,佩戴舒适,能够在各种环境下保持稳定的音质和传输效果。无论是户外拍摄、室内直播还是大型演出,无线领夹麦克风都能为你提供卓越的音频体验,让你的声音成为最亮眼的焦点。可要说哪款无线麦克风好用的,我推荐以下几款,都是我自用过觉得很不错的款式,可以参考下。 一、无线领夹麦克风这样挑选! ①选择专业有实力品牌,不选网红

在青春之后,认输之前,我依然用力的活着

今天是个值得纪念的日子。     2002年7月6号,也就是去年的今日,我执行了旅行计划表里的最后一个计划。先是到了陕西,我的大学所在地,我对陕西的感情就如同我的第二故乡,陕西事儿毕,正式踏上了前往西藏的旅途。我热爱旅行,尤其喜欢一个人的旅行,背上我那伴随我多年的迷彩包,如果要进行打工旅行,我会再加上我那从大学时代使用至今的大密码箱,一切准备就绪,踏上旅程,接触形形色色的人群,交往五湖四海的驴友,

心跳机制讲解及实例

什么是心跳机制 心跳机制出现在tcp长连接中,客户端和服务器之见定时发送一种特殊的数据包通知对方还在线,以确保tcp链接地可靠性,有可能tcp链接由于某些原因(列入网线被拔了,突然断电)导致客户端断了,但是服务器不知道客户端断了,服务器还保持与客户端连接的状态,所以为了不浪费资源,需要知道客户端非正常中断,服务器把断开客户端断开链接,需要加入心跳包机制 tcp 需不需要心跳? 需要心跳机制t