一、前言

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

    本文转载自:http://blog.csdn.net/yuanlulu/article/details/6438841 版权声明:本文为博主原创文章,未经博主允许不得转载. ============= ...

  2. dhclient命令

    语法:dhclient(选项)(参数) 选项0:指定dhcp客户但监听的端口号-d:总是以前台方式运行程序-q:安静模式,不打印任何错误的提示信息-r:释放ip地址 参数:网络接口:操作的网络接口 示 ...

  3. python sorted函数的小练习

    前两天学习了一下socket编程,在向某大神请教问题时被嫌弃了,有一种还没学会走就想跑的感觉.大神说我现在的水平应该去做一些像是操作文件.序列号等的小练习来加深理解.下面是他给我出的小练习: 1.da ...

  4. No java virtual machine ....

    运行Eclipse提示No java virtual machine   版权声明:本文原创作者:一叶飘舟 作者博客地址:http://blog.csdn.net/jdsjlzx http://blo ...

  5. MSD3393/MSD3463 屏参及REG对照表

    概述:TIMMING组成 MOD: BANK:0x1032 VOP: SC_BK10 注意BANK对应: VOP: SC_BK10 例如:MS_U16 m_wPanelHTotal;   Sub VO ...

  6. dos窗口的乱码问题

    1.在dos窗口上输入chcp 65001 2.然后右键属性选择字体

  7. HDU 1394 树状数组+离散化求逆序数

    对于求逆序数问题,学会去利用树状数组进行转换求解方式,是很必要的. 一般来说我们求解逆序数,是在给定一串序列里,用循环的方式找到每一个数之前有多少个比它大的数,算法的时间复杂度为o(n2). 那么我们 ...

  8. 用Merge存储引擎中间件实现MySQL分表

    觉得一个用Merge存储引擎中间件来实现MySQL分表的方法不错. 可以看下这个博客写的很清楚--> http://www.cnblogs.com/xbq8080/p/6628034.html ...

  9. Robot Framework基础学习(一)

    Robot Framework语法学习: 一.变量的声明.赋值与使用 1.变量标识符:每个变量都可以用  变量标识符 ${变量名} 来表示. 2.变量声明:可以在TestSuite上点右键或者在Edi ...

  10. A - Toy Cars

    Time Limit:1000MS     Memory Limit:262144KB     64bit IO Format:%I64d & %I64u Description Little ...