一、前言

心跳机制是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着,以确保连接的有效性的机制。
  我们用到的很多框架都用到了心跳检测,比如服务注册到 Eureka Server 之后会维护一个心跳连接,告诉 Eureka Server 自己还活着。本文就是利用 Netty 来实现心跳检测,以及客户端重连。

二、设计思路

  1. 分为客户端和服务端
  2. 建立连接后,客户端先发送一个消息询问服务端是否可以进行通信了。
  3. 客户端收到服务端 Yes 的应答后,主动发送心跳消息,服务端接收到心跳消息后,返回心跳应答,周而复始。
  4. 心跳超时利用 Netty 的 ReadTimeOutHandler 机制,当一定周期内(默认值50s)没有读取到对方任何消息时,需要主动关闭链路。如果是客户端,重新发起连接。
  5. 为了避免出现粘/拆包问题,使用 DelimiterBasedFrameDecoder 和 StringDecoder 来处理消息。

三、编码

  1. 先编写客户端 NettyClient
  1. public class NettyClient { 


  2. private static final String HOST = "127.0.0.1"; 


  3. private static final int PORT = 9911; 


  4. private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); 


  5. EventLoopGroup group = new NioEventLoopGroup(); 



  6. private void connect(String host,int port){ 

  7. try { 

  8. Bootstrap b = new Bootstrap(); 

  9. b.group(group) 

  10. .channel(NioSocketChannel.class) 

  11. .option(ChannelOption.TCP_NODELAY,true) 

  12. .remoteAddress(new InetSocketAddress(host,port)) 

  13. .handler(new ChannelInitializer<SocketChannel>() { 

  14. @Override 

  15. protected void initChannel(SocketChannel ch) throws Exception { 

  16. ByteBuf delimiter = Unpooled.copiedBuffer("$_", CharsetUtil.UTF_8); 

  17. ch.pipeline() 

  18. .addLast(new DelimiterBasedFrameDecoder(1024,delimiter)) 

  19. .addLast(new StringDecoder()) 

  20. // 当一定周期内(默认50s)没有收到对方任何消息时,需要主动关闭链接 

  21. .addLast("readTimeOutHandler",new ReadTimeoutHandler(50)) 

  22. .addLast("heartBeatHandler",new HeartBeatReqHandler()); 



  23. }); 

  24. // 发起异步连接操作 

  25. ChannelFuture future = b.connect().sync(); 

  26. future.channel().closeFuture().sync(); 

  27. }catch (Exception e){ 

  28. e.printStackTrace(); 

  29. }finally { 

  30. // 所有资源释放完之后,清空资源,再次发起重连操作 

  31. executor.execute(()->{ 

  32. try { 

  33. TimeUnit.SECONDS.sleep(5); 

  34. //发起重连操作 

  35. connect(NettyClient.HOST,NettyClient.PORT); 

  36. } catch (InterruptedException e) { 

  37. e.printStackTrace(); 



  38. }); 






  39. public static void main(String[] args) { 

  40. new NettyClient().connect(NettyClient.HOST,NettyClient.PORT); 






这里稍微复杂点的就是38行开始的重连部分。
2. 心跳消息发送类 HeartBeatReqHandler

  1. package cn.sp.heartbeat; 


  2. import io.netty.buffer.Unpooled; 

  3. import io.netty.channel.ChannelHandler; 

  4. import io.netty.channel.ChannelHandlerContext; 

  5. import io.netty.channel.SimpleChannelInboundHandler; 


  6. import java.util.concurrent.ScheduledFuture; 

  7. import java.util.concurrent.TimeUnit; 


  8. /** 

  9. * Created by 2YSP on 2019/5/23. 

  10. */ 

  11. @ChannelHandler.Sharable 

  12. public class HeartBeatReqHandler extends SimpleChannelInboundHandler<String> { 


  13. private volatile ScheduledFuture<?> heartBeat; 


  14. private static final String hello = "start notify with server$_"; 


  15. @Override 

  16. public void channelActive(ChannelHandlerContext ctx) throws Exception { 

  17. ctx.writeAndFlush(Unpooled.copiedBuffer(hello.getBytes())); 

  18. System.out.println("================"); 




  19. @Override 

  20. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 

  21. if (heartBeat != null){ 

  22. heartBeat.cancel(true); 

  23. heartBeat = null; 



  24. ctx.fireExceptionCaught(cause); 




  25. @Override 

  26. protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { 

  27. if ("ok".equalsIgnoreCase(msg)){ 

  28. //服务端返回ok开始心跳 

  29. heartBeat = ctx.executor().scheduleAtFixedRate(new HeartBeatTask(ctx),0,5000, TimeUnit.MILLISECONDS); 

  30. }else { 

  31. System.out.println("Client receive server heart beat message : --->"+msg); 






  32. private class HeartBeatTask implements Runnable{ 


  33. private final ChannelHandlerContext ctx; 


  34. public HeartBeatTask(ChannelHandlerContext ctx){ 

  35. this.ctx = ctx; 





  36. @Override 

  37. public void run() { 

  38. String heartBeat = "I am ok"; 

  39. System.out.println("Client send heart beat message to server: ----->"+heartBeat); 

  40. ctx.writeAndFlush(Unpooled.copiedBuffer((heartBeat+"$_").getBytes())); 








channelActive()方法在首次建立连接后向服务端问好,如果服务端返回了 "ok" 就创建一个线程每隔5秒发送一次心跳消息。如果发生了异常,就取消定时任务并将其设置为 null,等待 GC 回收。
3. 服务端 NettyServer

  1. public class NettyServer { 


  2. public static void main(String[] args) { 

  3. new NettyServer().bind(9911); 




  4. private void bind(int port){ 

  5. EventLoopGroup group = new NioEventLoopGroup(); 

  6. try { 

  7. ServerBootstrap b = new ServerBootstrap(); 

  8. b.group(group) 

  9. .channel(NioServerSocketChannel.class) 

  10. .childHandler(new ChannelInitializer<SocketChannel>() { 

  11. @Override 

  12. protected void initChannel(SocketChannel ch) throws Exception { 

  13. ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes()); 


  14. ch.pipeline() 

  15. .addLast(new DelimiterBasedFrameDecoder(1024,delimiter)) 

  16. .addLast(new StringDecoder()) 

  17. .addLast("readTimeOutHandler",new ReadTimeoutHandler(50)) 

  18. .addLast("HeartBeatHandler",new HeartBeatRespHandler()); 



  19. }); 

  20. // 绑定端口,同步等待成功 

  21. b.bind(port).sync(); 

  22. System.out.println("Netty Server start ok ...."); 

  23. }catch (Exception e){ 

  24. e.printStackTrace(); 







  1. 心跳响应类 HeartBeatRespHandler
  1. package cn.sp.heartbeat; 


  2. import io.netty.buffer.Unpooled; 

  3. import io.netty.channel.ChannelHandler; 

  4. import io.netty.channel.ChannelHandlerContext; 

  5. import io.netty.channel.SimpleChannelInboundHandler; 


  6. /** 

  7. * Created by 2YSP on 2019/5/23. 

  8. */ 

  9. @ChannelHandler.Sharable 

  10. public class HeartBeatRespHandler extends SimpleChannelInboundHandler<String> { 


  11. private static final String resp = "I have received successfully$_"; 


  12. @Override 

  13. protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { 

  14. if (msg.equals("start notify with server")){ 

  15. ctx.writeAndFlush(Unpooled.copiedBuffer("ok$_".getBytes())); 

  16. }else { 

  17. //返回心跳应答信息 

  18. System.out.println("Receive client heart beat message: ---->"+ msg); 

  19. ctx.writeAndFlush(Unpooled.copiedBuffer(resp.getBytes())); 









第一次告诉客户端我已经准备好了,后面打印客户端发过来的信息并告诉客户端我已经收到你的消息了。

四、测试

启动服务端再启动客户端,可以看到心跳检测正常,如下图。

服务端控制台

客户端控制台

现在让服务端宕机一段时间,看客户端能否重连并开始正常工作。

关闭服务端后,客户端周期性的连接失败,控制台输出如图:

连接失败

重新启动服务端,过一会儿发现重连成功了。

成功重连

五、总结

总得来说,使用 Netty 实现心跳检测还是比较简单的,这里比较懒没有使用其他序列化协议(如 ProtoBuf 等),如果感兴趣的话大家可以自己试试。
代码地址,点击这里
有篇SpringBoot 整合长连接心跳机制的文章写的也很不错,地址https://crossoverjie.top/2018/05/24/netty/Netty(1)TCP-Heartbeat/

【Netty】利用Netty实现心跳检测和重连机制的更多相关文章

  1. WebSocket心跳检测和重连机制

    1. 心跳重连原由 心跳和重连的目的用一句话概括就是客户端和服务端保证彼此还活着,避免丢包发生. websocket连接断开有以下两证情况: 前端断开 在使用websocket过程中,可能会出现网络断 ...

  2. 记录初试Netty(2)-服务端心跳检测

    今天在在搭建的netty框架中添加心跳机制,特此记录一下:      1.什么是心跳机制? 心跳是在TCP长连接中,客户端和服务端定时向对方发送数据包通知对方自己还在线,保证连接的有效性的一种机制 在 ...

  3. netty的数据通信之心跳检测

    问题1:我们想实现客户端和服务端建立连接之后,5秒钟之后如果没有数据传输就关闭与客户端的连接. 解决办法:在服务端加上下面一条代码 ch.pipeline().addLast(new ReadTime ...

  4. NETTY keeplive 参数,心跳检测

    当设置为true的时候,TCP会实现监控连接是否有效,当连接处于空闲状态的时候,超过了2个小时,本地的TCP实现会发送一个数据包给远程的 socket,如果远程没有发回响应,TCP会持续尝试11分钟, ...

  5. Netty — 心跳检测和断线重连

    一.前言 由于在通信层的网络连接的不可靠性,比如:网络闪断,网络抖动等,经常会出现连接断开.这样对于使用长连接的应用而言,当突然高流量冲击势必会造成进行网络连接,从而产生网络堵塞,应用响应速度下降,延 ...

  6. netty 实现心跳检查--断开重连--通俗易懂

    一.心跳介绍 网络中的接收和发送数据都是使用操作系统中的SOCKET进行实现.但是如果此套接字已经断开,那发送数据和接收数据的时候就一定会有问题. 1.心跳机制: 是服务端和客户端定时的发送一个心跳包 ...

  7. netty实现客户端服务端心跳重连

    前言: 公司的加密机调度系统一直使用的是http请求调度的方式去调度,但是会出现网络故障导致某个客户端或者服务端断线的情况,导致很多请求信息以及回执信息丢失的情况,接着我们抛弃了http的方式,改为T ...

  8. Netty实现服务端客户端长连接通讯及心跳检测

    通过netty实现服务端与客户端的长连接通讯,及心跳检测.        基本思路:netty服务端通过一个Map保存所有连接上来的客户端SocketChannel,客户端的Id作为Map的key.每 ...

  9. 通过netty实现服务端与客户端的长连接通讯,及心跳检测。

    基本思路:netty服务端通过一个Map保存所有连接上来的客户端SocketChannel,客户端的Id作为Map的key.每次服务器端如果要向某个客户端发送消息,只需根据ClientId取出对应的S ...

随机推荐

  1. Collection of Boot Sector Formats for ISO 9660 Images

    http://bazaar.launchpad.net/~libburnia-team/libisofs/scdbackup/view/head:/doc/boot_sectors.txt Colle ...

  2. [P2769] 猴子上树

    题目描述 在猴村有一条笔直的山路,这条山路很窄,宽度忽略不计.有 n只猴子正站在山路上静静地观望今天来参加比赛的各位同学.用一个正整数Xi表示第i只猴子所站位置,任意两只猴子的所站位置互不相同.在这条 ...

  3. [Usaco2005 Dec]Cleaning Shifts 清理牛棚

    题目描述 Farmer John's cows, pampered since birth, have reached new heights of fastidiousness. They now ...

  4. 基于地理位置信息的traceroute

    我们在机房选择.測试网络的质量的时候,往往仅仅依据跳数.延迟.抖动.网络吞吐量等指标来衡量,非常多时候跳数并不能全然显示网络拓扑优劣,于是写了个traceroute结合whois的小脚本来直观显示每一 ...

  5. Javascript的参数详解

    函数可以有参数也可以没有参数,如果定义了参数,在调用函数的时候没有传值,默认设置为undefined 在调用函数时如果传递参数超过了定义时参数,jS会忽略掉多余参数 jS中不能直接写默认值,可以通过a ...

  6. 【LeetCode】Binary Tree Inorder Traversal

    Binary Tree Inorder Traversal Total Accepted: 16406 Total Submissions: 47212My Submissions Given a b ...

  7. 自动化测试框架selenium+java+TestNG——TestNG详解

    TestNG按顺序执行case package com.testngDemo; import org.testng.annotations.AfterClass; import org.testng. ...

  8. IOS开发学习笔记(1)-----UILabel 详解

    1. [代码][C/C++]代码     //创建uilabelUILabel *label1 = [[UILabel alloc] initWithFrame:CGRectMake(20, 40, ...

  9. ssh整合 小例子

    实现了用户的查插删改操作. 原理:struts负责接收.分发请求.spring采用面向接口编程的思想整合整体框架,负责连接数据库.hibernate负责操作数据库语言. 思路: 1.配置struts的 ...

  10. 页面渲染——页面合成(composition)的优化

    合成(composition)意味着将网页中已经绘画好的部分结合在一起,且展示在屏幕上. 坚持使用transform和opacity属性来操作你的动画animation 在有动画的元素上使用 will ...