Netty心跳和重连
来源:互联网 发布:摩天手无线鼠标 知乎 编辑:程序博客网 时间:2024/05/16 10:44
转载自:http://blog.csdn.net/z69183787/article/details/52625095
Netty应用心跳和重连的整个过程:
1)客户端连接服务端;
2)在客户端的的ChannelPipeline中加入一个比较特殊的IdleStateHandler,设置一下客户端的写空闲时间,例如5s;
3)当客户端的所有ChannelHandler中4s内没有write事件,则会触发userEventTriggered方法;
4)在客户端的userEventTriggered中对应的触发事件下发送一个心跳包给服务端,检测服务端是否还存活,防止服务端已经宕机,客户端还不知道;
5)同样,服务端要对心跳包做出响应,其实给客户端最好的回复就是“不回复”,这样可以降低服务端的压力,假如有10w个空闲Idle的连接,那么服务端光发送心跳回复,则也是费事的事情,那么怎么才能告诉客户端它还活着呢,其实很简单,因为5s服务端都会收到来自客户端的心跳信息,那么如果10秒内收不到,服务端可以认为客户端挂了,可以close链路;
6)假如服务端因为什么因素导致宕机的话,就会关闭所有的链路链接,所以作为客户端要做的事情就是断线重连。
IdleStateHandler是Netty提供了对心跳机制的支持,心跳可以检测远程端是否存活,或者活跃。
IdleStateHandler内部有定时任务,会根据设置的超时参数的类型和值,检测channelRead和write方法多久没有被调用了,如果这个时间超过了设置的值,那么就会触发对应的事件,read触发read,write触发write,all触发all。如果超时了,则会调用userEventTriggered方法,且会告诉超时的类型;如果没有超时,则会循环定时检测,除非将IdleStateHandler移除Pipeline。
只有链路建立之后,即channelActive事件触发之后,才会initialize内部的定时任务。
要实现可靠的心跳重连,需要对ChannelPipeline中的ChannelHandlers的维护,首次连接和重连都需要对ChannelHandlers进行管理;对重连对象的管理,也就是bootstrap对象的管理;还有重连机制。接下来将会以简单的例子来说明。
首先先定义一个接口ChannelHandlerHolder,用来保管ChannelPipeline中的Handlers。
实现了2个接口,TimeTask,ChannelHandlerHolder,TimeTask就要重写run方法,这应该是一个定时任务,这个定时任务做的事情应该是重连的工作;ChannelHandlerHolder的接口,这个接口维护的所有的Handlers,因为在重连的时候需要获取Handlers;
bootstrap对象,重连的时候依旧需要这个对象;
当链路断开的时候会触发channelInactive这个方法,也就说触发重连的导火索是从这边开始的。
接下来就是客户端的Bootstrap:
Netty应用心跳和重连的整个过程:
1)客户端连接服务端;
2)在客户端的的ChannelPipeline中加入一个比较特殊的IdleStateHandler,设置一下客户端的写空闲时间,例如5s;
3)当客户端的所有ChannelHandler中4s内没有write事件,则会触发userEventTriggered方法;
4)在客户端的userEventTriggered中对应的触发事件下发送一个心跳包给服务端,检测服务端是否还存活,防止服务端已经宕机,客户端还不知道;
5)同样,服务端要对心跳包做出响应,其实给客户端最好的回复就是“不回复”,这样可以降低服务端的压力,假如有10w个空闲Idle的连接,那么服务端光发送心跳回复,则也是费事的事情,那么怎么才能告诉客户端它还活着呢,其实很简单,因为5s服务端都会收到来自客户端的心跳信息,那么如果10秒内收不到,服务端可以认为客户端挂了,可以close链路;
6)假如服务端因为什么因素导致宕机的话,就会关闭所有的链路链接,所以作为客户端要做的事情就是断线重连。
IdleStateHandler是Netty提供了对心跳机制的支持,心跳可以检测远程端是否存活,或者活跃。
public IdleStateHandler( long readerIdleTime, long writerIdleTime, long allIdleTime, TimeUnit unit) {构造函数中readerIdleTime为读超时时间,writerIdleTime为写超时时间,allIdleTime为所有类型的超时时间。这个类也是一个ChannelHandler,也需要被载入到ChannelPipeline中,加入在服务器端的ChannelInitializer中的代码如下:
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS)); // 字符串 解码 和 编码 pipeline.addLast(new StringDecoder());//解码 pipeline.addLast(new StringEncoder());//编码 // 自定义Handler pipeline.addLast(new HelloServerHandler()); } });这里表示服务器端会每隔5秒来检查一下channelRead方法被调用的情况,如果在5秒内该链上的channelRead方法都没有被触发,就会调用userEventTriggered方法。
IdleStateHandler内部有定时任务,会根据设置的超时参数的类型和值,检测channelRead和write方法多久没有被调用了,如果这个时间超过了设置的值,那么就会触发对应的事件,read触发read,write触发write,all触发all。如果超时了,则会调用userEventTriggered方法,且会告诉超时的类型;如果没有超时,则会循环定时检测,除非将IdleStateHandler移除Pipeline。
只有链路建立之后,即channelActive事件触发之后,才会initialize内部的定时任务。
要实现可靠的心跳重连,需要对ChannelPipeline中的ChannelHandlers的维护,首次连接和重连都需要对ChannelHandlers进行管理;对重连对象的管理,也就是bootstrap对象的管理;还有重连机制。接下来将会以简单的例子来说明。
首先先定义一个接口ChannelHandlerHolder,用来保管ChannelPipeline中的Handlers。
/** * * 客户端的ChannelHandler集合,由子类实现,这样做的好处: * 继承这个接口的所有子类可以很方便地获取ChannelPipeline中的Handlers * 获取到handlers之后方便ChannelPipeline中的handler的初始化和在重连的时候也能很方便 * 地获取所有的handlers */public interface ChannelHandlerHolder { ChannelHandler[] handlers();}服务端需要一个AcceptorIdleStateTrigger,继承ChannelInboundHandlerAdapter,重写userEventTriggered方法,因为客户端是write,那么服务端自然是read,设置的状态就是IdleState.READER_IDLE,代码如下:
@ChannelHandler.Sharablepublic class AcceptorIdleStateTrigger extends ChannelInboundHandlerAdapter { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof IdleStateEvent) { IdleState state = ((IdleStateEvent) evt).state(); if (state == IdleState.READER_IDLE) { throw new Exception("Idle exception"); } } else { super.userEventTriggered(ctx, evt); } }}然后自定义一个简单的业务处理的Handler:
public class HeartBeatServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("server channelRead.."); System.out.println(ctx.channel().remoteAddress() + "->Server :" + msg.toString()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); }}再来编写服务端的ServerBootstrap代码:
public class HeartBeatServer { private final AcceptorIdleStateTrigger idleStateTrigger = new AcceptorIdleStateTrigger(); private int port; public HeartBeatServer(int port) { this.port = port; } public void start() { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap sbs = new ServerBootstrap().group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO)) .localAddress(new InetSocketAddress(port)).childHandler(new ChannelInitializer<SocketChannel>() { protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS)); ch.pipeline().addLast(idleStateTrigger); ch.pipeline().addLast("decoder", new StringDecoder()); ch.pipeline().addLast("encoder", new StringEncoder()); ch.pipeline().addLast(new HeartBeatServerHandler()); }; }).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true); // 绑定端口,开始接收进来的连接 ChannelFuture future = sbs.bind(port).sync(); System.out.println("Server start listen at " + port); future.channel().closeFuture().sync(); } catch (Exception e) { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception { int port; if (args.length > 0) { port = Integer.parseInt(args[0]); } else { port = 8080; } new HeartBeatServer(port).start(); }}服务端就完成了,下面是客户端。首先是ConnectorIdleStateTrigger:
@ChannelHandler.Sharablepublic class ConnectorIdleStateTrigger extends ChannelInboundHandlerAdapter { private static final ByteBuf HEARTBEAT_SEQUENCE = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Heartbeat", CharsetUtil.UTF_8)); @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof IdleStateEvent) { IdleState state = ((IdleStateEvent) evt).state(); if (state == IdleState.WRITER_IDLE) { // write heartbeat to server ctx.writeAndFlush(HEARTBEAT_SEQUENCE.duplicate()); } } else { super.userEventTriggered(ctx, evt); } }}简单的自定义的Handler:
@ChannelHandler.Sharablepublic class HeartBeatClientHandler extends ChannelInboundHandlerAdapter { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("激活时间是:" + new Date()); System.out.println("HeartBeatClientHandler channelActive"); ctx.fireChannelActive(); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("停止时间是:" + new Date()); System.out.println("HeartBeatClientHandler channelInactive"); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { String message = (String) msg; System.out.println(message); if (message.equals("Heartbeat")) { ctx.write("has read message from server"); ctx.flush(); } ReferenceCountUtil.release(msg); }}接下来就是重点,客户端需要定义一个类,这个类可以去观察链路是否断了,如果断了,进行循环的断线重连操作,ConnectionWatchdog,顾名思义,链路检测狗,先看完整代码:
@ChannelHandler.Sharablepublic abstract class ConnectionWatchdog extends ChannelInboundHandlerAdapter implements TimerTask, ChannelHandlerHolder { private final Bootstrap bootstrap; private final Timer timer; private final int port; private final String host; private volatile boolean reconnect = true; private int attempts; public ConnectionWatchdog(Bootstrap bootstrap, Timer timer, int port, String host, boolean reconnect) { this.bootstrap = bootstrap; this.timer = timer; this.port = port; this.host = host; this.reconnect = reconnect; } /** * channel链路每次active的时候,将其连接的次数重新置0 */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("当前链路已经激活了,重连尝试次数重新置为0"); attempts = 0; ctx.fireChannelActive(); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("链接关闭"); if (reconnect) { System.out.println("链接关闭,将进行重连"); if (attempts < 12) { attempts++; } //重连的间隔时间会越来越长 int timeout = 2 << attempts; timer.newTimeout(this, timeout, TimeUnit.MILLISECONDS); } ctx.fireChannelInactive(); } @Override public void run(Timeout timeout) throws Exception { ChannelFuture future; //bootstrap已经初始化好了,只需要将handler填入就可以了 synchronized (bootstrap) { bootstrap.handler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(handlers()); } }); future = bootstrap.connect(host, port); } //future对象 future.addListener(new ChannelFutureListener() { public void operationComplete(ChannelFuture f) throws Exception { boolean succeed = f.isSuccess(); //如果重连失败,则调用ChannelInactive方法,再次出发重连事件,一直尝试12次,如果失败则不再重连 if (!succeed) { System.out.println("重连失败"); f.channel().pipeline().fireChannelInactive(); } else { System.out.println("重连成功"); } } }); }}ConnectionWatchdog继承了ChannelInboundHandlerAdapter,说明它也是Handler;
实现了2个接口,TimeTask,ChannelHandlerHolder,TimeTask就要重写run方法,这应该是一个定时任务,这个定时任务做的事情应该是重连的工作;ChannelHandlerHolder的接口,这个接口维护的所有的Handlers,因为在重连的时候需要获取Handlers;
bootstrap对象,重连的时候依旧需要这个对象;
当链路断开的时候会触发channelInactive这个方法,也就说触发重连的导火索是从这边开始的。
接下来就是客户端的Bootstrap:
public class HeartBeatsClient { protected final HashedWheelTimer timer = new HashedWheelTimer(); private Bootstrap boot; private final ConnectorIdleStateTrigger idleStateTrigger = new ConnectorIdleStateTrigger(); public void connect(int port, String host) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); boot = new Bootstrap(); boot.group(group) .channel(NioSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)); final ConnectionWatchdog watchdog = new ConnectionWatchdog(boot, timer, port, host, true) { public ChannelHandler[] handlers() { return new ChannelHandler[]{ this, new IdleStateHandler(0, 4, 0, TimeUnit.SECONDS), idleStateTrigger, new StringDecoder(), new StringEncoder(), new HeartBeatClientHandler() }; } }; ChannelFuture future; try { synchronized (boot) {//进行连接 boot.handler(new ChannelInitializer<Channel>() { //初始化channel @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(watchdog.handlers()); } }); future = boot.connect(host, port); } // 以下代码在synchronized同步块外面是安全的 future.sync(); } catch (Throwable t) { throw new Exception("connects to fails", t); } } public static void main(String[] args) throws Exception { int port = 8000; new HeartBeatsClient().connect(port, "127.0.0.1"); }}
阅读全文
0 0
- Netty心跳和重连
- Netty 4.0 实现心跳检测和断线重连
- Netty 4.0 实现心跳检测和断线重连
- Netty生产级的心跳和重连机制
- 一起学Netty(十四)之 Netty生产级的心跳和重连机制
- Netty 之 Netty生产级的心跳和重连机制
- 一起学Netty(十四)之 Netty生产级的心跳和重连机制
- 一起学Netty(十四)之 Netty生产级的心跳和重连机制
- 一起学Netty(十四)之 Netty生产级的心跳和重连机制
- netty 心跳与IdleStateHandler与断线重连
- 浅析 Netty 实现心跳机制与断线重连
- 浅析Netty实现心跳机制与断线重连
- 初探和实现websocket心跳重连
- 长连接、心跳和断线重连
- 初探和实现websocket心跳重连
- 初探和实现websocket心跳重连
- websocke心跳重连
- Netty断线重连
- AndroidStudio/gradle 配置打包输出文件名/输出版本号渠道
- BZOJ2683: 简单题
- Object和Objects的区别
- Centos window界面与命令行
- mysql在linux下的安装
- Netty心跳和重连
- 开发中遇到的坑
- Java简史
- java注解详解
- 功能强大的C语言memset()函数用法
- 区间DP(记搜)——BZOJ1032/Luogu2145 [JSOI2007]祖码Zuma
- 13.OP-TEE OS启动(四)--service_init_late
- AM335X SD卡分区制作
- java-反射