Netty心跳机制-长连接

前文需求回顾

完成对红酒窖的室内温度采集及监控功能。由本地应用程序+温度传感器定时采集室内温度上报至服务器,如果温度 >20 °C 则由服务器下发重启空调指令,如果本地应用长时间不上传温度给服务器,则给户主手机发送一条预警短信。


Netty入门篇-从双向通信开始「上文」

上篇算是完成简单的双向通信了,我们接着看看 “如果本地应用长时间不上传温度给服务器…”,很明显客户端有可能挂了嘛,所以怎么实现客户端与服务端的长连接就是本文要实现的了。

什么是心跳机制

百度百科:心跳机制是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着,以确保连接的有效性的机制。

简单说,这个心跳机制是由客户端主动发起的消息,每隔一段时间就向服务端发送消息,告诉服务端自己还没死,可不要给户主发送预警短信啊。

如何实现心跳机制

1、客户端代码修改

我们需要改造一下上节中客户端的代码,首先是在责任链中增加一个心跳逻辑处理类HeartbeatHandler

public class NettyClient {private static String host = "127.0.0.1";public static void main(String[] args) {NioEventLoopGroup workerGroup = new NioEventLoopGroup();Bootstrap bootstrap = new Bootstrap();bootstrap// 1.指定线程模型.group(workerGroup)// 2.指定 IO 类型为 NIO.channel(NioSocketChannel.class).option(ChannelOption.SO_KEEPALIVE, true).option(ChannelOption.TCP_NODELAY, true)// 3.IO 处理逻辑.handler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) {ch.pipeline().addLast(new IdleStateHandler(0, 10, 0)).addLast(new StringDecoder()).addLast(new StringEncoder()).addLast(new HeartbeatHandler()).addLast(new NettyClientHandler());}});// 4.建立连接bootstrap.connect(host, 8070).addListener(future -> {if (future.isSuccess()) {System.out.println("连接成功!");} else {System.err.println("连接失败!");}});}
}

没什么变化,主要是增加了HeartbeatHandler,我们来看看这个类:

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import java.nio.charset.Charset;
import java.time.LocalTime;public class HeartbeatHandler extends ChannelInboundHandlerAdapter {@Overridepublic void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {if (evt instanceof IdleStateEvent) {IdleStateEvent idleStateEvent = (IdleStateEvent) evt;if (idleStateEvent.state() == IdleState.WRITER_IDLE) {System.out.println("10秒了,需要发送消息给服务端了" + LocalTime.now());//向服务端送心跳包ByteBuf buffer = getByteBuf(ctx);//发送心跳消息,并在发送失败时关闭该连接ctx.writeAndFlush(buffer).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);}} else {super.userEventTriggered(ctx, evt);}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {System.out.println("捕获的异常:" + cause.getMessage());ctx.channel().close();}private ByteBuf getByteBuf(ChannelHandlerContext ctx) {// 1. 获取二进制抽象 ByteBufByteBuf buffer = ctx.alloc().buffer();String time = "heartbeat:客户端心跳数据:" + LocalTime.now();// 2. 准备数据,指定字符串的字符集为 utf-8byte[] bytes = time.getBytes(Charset.forName("utf-8"));// 3. 填充数据到 ByteBufbuffer.writeBytes(bytes);return buffer;}}

还是继承自ChannelInboundHandlerAdapter,不过这次重写的是userEventTriggered()方法,这个方法在客户端的所有ChannelHandler中,如果10s内没有发生write事件时触发,所以我们在该方法中给服务端发送心跳消息。

业务逻辑处理类NettyClientHandler没有改动,代码如下:

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.Random;public class NettyClientHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelActive(ChannelHandlerContext ctx) {System.out.println(new Date() + ": 客户端写出数据");// 1. 获取数据ByteBuf buffer = getByteBuf(ctx);// 2. 写数据ctx.channel().writeAndFlush(buffer);}private ByteBuf getByteBuf(ChannelHandlerContext ctx) {// 1. 获取二进制抽象 ByteBufByteBuf buffer = ctx.alloc().buffer();Random random = new Random();double value = random.nextDouble() * 14 + 8;String temp = "获取室内温度:" + value;// 2. 准备数据,指定字符串的字符集为 utf-8byte[] bytes = temp.getBytes(Charset.forName("utf-8"));// 3. 填充数据到 ByteBufbuffer.writeBytes(bytes);return buffer;}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println(new Date() + ": 客户端读到数据 -> " + msg.toString());}}

对如上代码不了解的可以回看上一节:Netty入门篇-从双向通信开始

2、服务端代码修改

服务端代码主要是开启TCP底层心跳机制支持,.childOption(ChannelOption.SO_KEEPALIVE, true) ,其他的代码并没有改动:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;public class NettyServer {public static void main(String[] args) {NioEventLoopGroup bossGroup = new NioEventLoopGroup();NioEventLoopGroup workerGroup = new NioEventLoopGroup();ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(bossGroup, workerGroup)// 指定Channel.channel(NioServerSocketChannel.class)//服务端可连接队列数,对应TCP/IP协议listen函数中backlog参数.option(ChannelOption.SO_BACKLOG, 1024)//设置TCP长连接,一般如果两个小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文.childOption(ChannelOption.SO_KEEPALIVE, true)//将小的数据包包装成更大的帧进行传送,提高网络的负载.childOption(ChannelOption.TCP_NODELAY, true).childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) {ch.pipeline().addLast(new NettyServerHandler());}});serverBootstrap.bind(8070);}}

我们再来看看服务端的业务处理类 NettyServerHandler

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.nio.charset.Charset;
import java.util.Date;public class NettyServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {ByteBuf byteBuf = (ByteBuf) msg;String message = byteBuf.toString(Charset.forName("utf-8"));System.out.println(new Date() + ": 服务端读到数据 -> " + message);/** 心跳数据是不发送数据 **/if(!message.contains("heartbeat")){ByteBuf out = getByteBuf(ctx);ctx.channel().writeAndFlush(out);}}private ByteBuf getByteBuf(ChannelHandlerContext ctx) {byte[] bytes = "我是发送给客户端的数据:请重启冰箱!".getBytes(Charset.forName("utf-8"));ByteBuf buffer = ctx.alloc().buffer();buffer.writeBytes(bytes);return buffer;}}

channelRead() 方法增加了一个 if 判断,判断如果包含heartbeat字符串就认为这是客户端发过来的心跳,这种判断是非常low的,因为到目前为止我们一直是用简单字符串来传递数据的,上边传递的数据就直接操作字符串;那么问题来了,如果我们想传递对象怎么搞呢?下节写。我们先来看一下如上代码客户端与服务端运行截图:

服务端

客户端

至此,整个心跳机制就完成了,这样每隔10秒客户端就会给服务端发送一个心跳消息,下节我们通过了解通协议以完善心跳机制的代码。

18年专科毕业后,我创建了一个java相关的公众号,用来记录自己的学习之路,感兴趣的小伙伴可以关注一下:小伟后端笔记

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/413965.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

带你反编译APP然后重新打包「MacOS」

最近有小伙伴留言&#xff0c;怎么把一款APP改成自己的信息呀&#xff0c;咳咳&#xff0c;这又来送题材了&#xff0c;今天水一把APP反编译回编译&#xff0c;文中会针对一款APP进行简单的修改信息&#xff0c;问问题的小伙伴还不火速右上角支持一下。 MacOS跟Windows我会分开…

Hadoop学习之pig

首先明确pig是解决什么问题而出现的&#xff0c;pig是为了简化mapreduce编程而设计的&#xff0c;并且有自己的一套脚本语言。其基本由命令和操作符来定义的&#xff0c;如load&#xff0c;store&#xff0c;它的功能很明确&#xff0c;用来大规模处理数据。其脚本形如script.p…

[js] js怎样避免原型链上的对象共享?

[js] js怎样避免原型链上的对象共享&#xff1f; 组合继承 优势 公有的写在原型 私有的卸载构造函数 可以向父类传递参数劣势 需要手动绑定constructor 封装性一般 重复调用父类性能损耗&#x1f330; function Parent (name, friends) {// 私有的部分this.name name;this…

Codeforces 552C Vanya and Scales(进制转换+思维)

题目链接&#xff1a;http://codeforces.com/problemset/problem/552/C 题目大意&#xff1a;有101个砝码重量为w^0,w^1&#xff0c;....&#xff0c;w^100和一个重量为m的物体&#xff0c;问能否在天平两边放物品和砝码使其平衡。解题思路&#xff1a;将m化为w进制的数&#x…

反编译一款APP然后重新打包(Windows环境)

最近有小伙伴私信我&#xff0c;怎么把一款APP改成自己的信息呀&#xff0c;咳咳&#xff0c;这又来送题材了&#xff0c;今天水一把APP反编译回编译&#xff0c;文中会针对一款APP进行简单的修改信息&#xff0c;问问题的小伙伴还不火速右上角支持一下。 MacOS跟Windows我是分…

[js] 写一个方法,实时验证input输入的值是否满足金额如:3.56(最多只有两位小数且只能数字和小数点)的格式,其它特殊字符禁止输入

[js] 写一个方法&#xff0c;实时验证input输入的值是否满足金额如&#xff1a;3.56(最多只有两位小数且只能数字和小数点)的格式&#xff0c;其它特殊字符禁止输入 <body><input type"text" id"amount"><em id"message"><…

Vmware下CentOs7 桥接模式下配置固定IP

1.安装完CentOS7后 修改虚拟机网络适配器配置&#xff1a;改成桥接模式 2.设置Vmware的 编辑->虚拟网络编辑器 3.重启CentOs7 , 查看网络 # ip addr interface是&#xff1a; ens33 4.查看本地真实机ip&#xff0c;然后配置虚拟机固定ip windows 进入命令行模式cmd&#xf…

[js] 使用delete删除数组,其长度会改变吗

[js] 使用delete删除数组&#xff0c;其长度会改变吗 使用delete删除数组元素&#xff0c;其长度会改变吗&#xff1f;咱来写个案例&#x1f330;看看就知道了&#xff1a;var arr [1, 2, 3] delete arr[1] console.log(arr) console.log(arr.length)结果如下&#xff1a;通过…

MacOS svn:E230001 Can‘t use Subversion command line client: svn The path to the Subversion executabl

注意&#xff1a;本文仅针对于 MacOS 系统。 错误信息如下&#xff1a; Cant use Subversion command line client: svn The path to the Subversion executable is probably wrong. Fix it.好家伙&#xff0c;今天发现 IDEA 中的 SVN 突然不能用了… 因为之前的 SVN 是使用 …

Seurat | 单细胞分析工具

Seurat是一个老牌的单细胞分析工具了&#xff08;satija的力作&#xff09;&#xff0c;我之前测试过&#xff0c;但是没怎么用。 最近发现这个工具又publish在了NBT上&#xff0c;所以很有必要看一下这篇文章。 Integrating single-cell transcriptomic data across different…

[js] 代码中如果遇到未定义的变量,会抛出异常吗?程序还会不会继续往下走?

[js] 代码中如果遇到未定义的变量&#xff0c;会抛出异常吗&#xff1f;程序还会不会继续往下走&#xff1f; 在浏览器环境下JS 解析器解析到未定义变量时&#xff0c;会抛出 Uncaught ReferenceError 错误&#xff0c;JS 引擎会停止解析后面的代码&#xff0c;但之前的代码不…

创建线程都有哪些方式?— Callable篇

今天我们来看一道面试题引发的思考 问&#xff1a; 创建线程都有哪些方式&#xff1f; 答&#xff1a; 我了解的有四种创建方式&#xff1a; 继承Thread类创建线程类通过Runnable接口创建线程类通过Callable和Future创建线程通过线程池创建 相信大家回答这个问题没什么难度吧…

[js] 说说你对JSBridge的理解

[js] 说说你对JSBridge的理解 js和原生应用之间交互的桥梁个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 与歌谣一起通关前端面试题

ASP.NET Core 网站发布到Linux服务器

长期以来&#xff0c;使用.NET开发的应用只能运行在Windows平台上面&#xff0c;而目前国内蓬勃发展的互联网公司由于成本的考虑&#xff0c;大量使用免费的Linux平台&#xff0c;这就使得.NET空有一身绝技但无法得到广大的施展空间&#xff0c;.NET平台被认为只适合开发企业内…

如何暂停一个正在运行的线程?

今天把小伙伴问懵了&#xff0c;小刚&#xff0c;你知道怎么停止一个线程吗&#xff1f; 这…&#xff0c;这…&#xff0c;stop&#xff1f; 原来平时小刚这小子只知道创建线程&#xff0c;不知道怎么暂停线程呀~[狗头] 停止线程是在多线程开发中很重要的技术点&#xff0c;…

[js] js循环中调用异步的方法,如何确保执行结果的顺序是正确的?

[js] js循环中调用异步的方法&#xff0c;如何确保执行结果的顺序是正确的&#xff1f; An example:let searchApi function(arg){return new Promise((resolve,reject)>{setTimeout(()>{console.log(arg)resolve(arg)}, 20)}) }; let fields [1,2,3,4]; let arr new…

Page Cache的落地问题

除非特别说明&#xff0c;否则本文提到的写操作都是 buffer write/write back。 起因 前几天讨论到一个问题&#xff1a;Linux 下文件 close成功&#xff0c;会不会触发 “刷盘”&#xff1f; 其实这个问题根本不用讨论&#xff0c;查一下就知道。 man 2 close 的 NOTES 一节里…

Docker中搭建FastDFS文件系统(多图)

关于FastDFS FastDFS 是以 C 语言开发的一项开源轻量级分布式文件系统&#xff0c;他对文件进行管理&#xff0c;主要功能有&#xff1a;文件存储&#xff0c;文件同步&#xff0c;文件访问&#xff08;文件上传/下载&#xff09;等&#xff0c;特别适合以文件为载体的在线服务…

[js] 写一个方法遍历指定对象的所有属性

[js] 写一个方法遍历指定对象的所有属性 Object.keys()、Object.values()只能遍历对象自有的属性&#xff0c;for in 可以遍历原型中的属性。个人简介 我是歌谣&#xff0c;欢迎和大家一起交流前后端知识。放弃很容易&#xff0c; 但坚持一定很酷。欢迎大家一起讨论 主目录 …

jquery(2)

获得内容 - text()、html() 以及 val() 三个简单实用的用于 DOM 操作的 jQuery 方法&#xff1a;text() - 设置或返回所选元素的文本内容 ----不能识别标签 html() - 设置或返回所选元素的内容&#xff08;包括 HTML 标记&#xff09; val() - 设置或返回表单字段的值----- 即登…