一、前言

心跳机制是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着,以确保连接的有效性的机制。
  我们用到的很多框架都用到了心跳检测,比如服务注册到 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. SpringBoot-(8)-配置MySQL数据库链接,配置数据坚挺拦截,创建默认数据表

    一,链接mysql数据库 # 数据源基本配置 spring.datasource.username=root spring.datasource.password=123456 spring.data ...

  2. STM32 ~ ili9341 横屏驱动代码

    void ili9341_Initializtion(void) { u16 i; RCC->APB2ENR|=<<; //使能PORTB时钟 GPIOB->CRH&= ...

  3. (linux)struct inode 和 struct file

    转自:http://www.cnblogs.com/QJohnson/archive/2011/06/24/2089414.html 1.struct inode──字符设备驱动相关的重要结构介绍 内 ...

  4. Ubuntu 12.04安装grub2过程中出错怎么办【转】

    本文转载自:https://zhidao.baidu.com/question/491448169666671012.html 其实是可以不用优盘启动的.但使用优盘没有风险.你的系统虽然出现==不能安 ...

  5. Memory Notification: Library Cache Object loaded into SGA

    问题现象: 数据库服务器可以ping通,但SSH连接不了:应用.plsqldeveloper 也都连接不了.事情到了这个地步,只能重启服务器. 服务器环境:oracle10.2.0.1 +rhel5. ...

  6. ansible管理windows实践

    一.前言 近期打算搞搞自动部署,因为是windows服务器,一些工具和系统支持都不是太好.最后发现ansible比较火,最重要的是他支持windows.本文主要就ansible 在windows使用环 ...

  7. 跨域传输信息postMessage

    widnow.postMessage()方法允许安全的跨域传输. Syntax otherWindow.postMessage(message, targetOrigin, [transfer]); ...

  8. nyoj 1030 hihocoder 1338

    题目链接1: 点这里打开. 题目链接2:   点击打开链接 思路:dp,dp[i][j] 表示某个人在区间 i,j上的得分. sum数组表示前 n 项和, num 数组用来存输入的数字. 因为取数字是 ...

  9. BZOJ-4488:最大公约数(GCD)

    给定一个长度为 N 的正整数序列Ai对于其任意一个连续的子序列{Al,Al+1...Ar},我们定义其权值W(L,R )为其长度与序列中所有元素的最大公约数的乘积,即W(L,R) = (R-L+1) ...

  10. Swift类和结构体

    在C++中,相信不会有太多人去详细考究结构体和类的区别,因为二者关系实在不大.但在Swift中,结构体和类的关系非常大,它们的组成部分都包括:初始化器.实例方法.实例属性.类型属性.类型方法等等:二者 ...