一、前言

心跳机制是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着,以确保连接的有效性的机制。
  我们用到的很多框架都用到了心跳检测,比如服务注册到 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. root无权限删除 原因 进程 占用 文件

    [root@test opt]# find / | grep gitlab | xargs rm -rfrm: cannot remove ‘/sys/fs/cgroup/devices/system ...

  2. bc - An arbitrary precision calculator language

    bc(1) General Commands Manual bc(1) NAME bc - An arbitrary precision calculator language SYNTAX bc [ ...

  3. 寻找JAVA_HOME find java

    more apache-flume-1.8.0-bin/bin/flume-ng # find java if [ -z "${JAVA_HOME}" ] ; then warn ...

  4. 5 Ways to Make Your Hive Queries Run Faster

    5 Ways to Make Your Hive Queries Run Faster Technique #1: Use Tez  Hive can use the Apache Tez execu ...

  5. Windows10搭建FTP服务器

    配置FTP服务器步骤: 第一步: 打开控制面板--->选择程序--->启动或关闭Windows功能--->勾选FTP服务器等.如下图: 第二步: 右键此电脑--->点击管理-- ...

  6. "makefile:5: *** missing separator. Stop."【转】

    本文转载自:http://blog.csdn.net/fireroll/article/details/8607903 写makefile时出现这个错误提示 是表示makefile中的命令前没有使用T ...

  7. hihocoder #1094 : Lost in the City微软苏州校招笔试 12月27日 (建图不大【暴力枚举】 子图的4种形态 1Y )

    #1094 : Lost in the City 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 Little Hi gets lost in the city. He ...

  8. Gym - 100187A A - Potion of Immortality —— 贪心

    题目链接:http://codeforces.com/gym/100187/problem/A 题解: 光题意就想了很久:在最坏情况下的最小兔子数.其实就是至少用几只兔子就一定能找出仙药(答案存在的话 ...

  9. yii中调取字段名称时label与labelEx的区别

    $form = $this->beginWidget('CActiveForm',array('id' => 'userRegisterForm')); echo $form->la ...

  10. codeforces 702A A. Maximum Increase(水题)

    题目链接: A. Maximum Increase time limit per test 1 second memory limit per test 256 megabytes input sta ...