一、前言

心跳机制是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着,以确保连接的有效性的机制。
  我们用到的很多框架都用到了心跳检测,比如服务注册到 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. Golang RPC 性能测试

    Golang RPC 性能测试 | KDF5000 http://kdf5000.com/2017/03/28/Golang-RPC-性能测试/

  2. sam模板

    SAM模板 struct SAM{ * ; struct node{ node*nxt[],*fail; int len; }; node*root;int cnt; node no[maxn]; n ...

  3. virtualBox 不能开启一个新任务的错误

    2016.06.05 这两天想在virtualbox上安装CentOS7.0玩,遇到一个问题: 不能为虚拟电脑 CentOS7 打开一个新任务. The virtual machine 'CentOS ...

  4. CMTime 与 CMTimeMakeWithSeconds

    1.首先先看代码,这段代码的作用就是要让视频播放区域有个封面.不会显的太空当. - (void)avPlayerDidPlayed:(NSNotification *)noti { [_avPlaye ...

  5. 激活win10企业长期服务版

    win10 2016 长期服务版的ISO文件中本身就带有KMS激活KEY,不用输入任何KEY,连接网络进入CMD,只要输入:slmgr /skms kms.digiboy.irslmgr /ato这两 ...

  6. Spring注解原理的详细剖析与实现

    本文主要分为三部分: 一. 注解的基本概念和原理及其简单实用 二. Spring中如何使用注解 三. 编码剖析spring@Resource的实现原理 一.注解的基本概念和原理及其简单实用 注解(An ...

  7. 如何强制ffmpeg编码时输出一个关键帧

    http://blog.csdn.net/ashlingr/article/details/7829429 如何强制ffmpeg编码时输出一个关键帧   如何强制ffmpeg编码时输出一个关键帧 AV ...

  8. 生成chm格式帮助文档的步骤

    开场前,道具先得被齐全了. 道具:struts2的开源代码(以生成struts2的帮助文档为例).chm格式生成工具jd2chm.exe(网上有) 好了,准备演出 1.在eclipse中新建一个jav ...

  9. linux应用之mysql数据库指定版本的yum安装(centos)

    A Quick Guide to Using the MySQL Yum Repository Abstract The MySQL Yum repository provides RPM packa ...

  10. php排序方法之冒泡排序

    //冒泡排序法 $arr = array(3,55,45,2,67,76,6.7,-65,85,4); function bubblingSort($arr){ for ( $i=0; $i<c ...