Netty系列(4)Netty心跳检测机制

Netty心跳检测机制

1 心跳检测使用场景

长连接的应用场景非常的广泛,比如监控系统,IM系统,即时报价系统,推送服务等等。像这些场景都是比较注重实时性,如果每次发送数据都要进行一次DNS解析,建立连接的过程肯定是极其影响体验。

而长连接的维护必然需要一套机制来控制。比如 HTTP/1.0 通过在 header 头中添加 Connection:Keep-Alive参数,如果当前请求需要保活则添加该参数作为标识,否则服务端就不会保持该连接的状态,发送完数据之后就关闭连接。HTTP/1.1以后 Keep-Alive 是默认打开的。

Netty 是 基于 TCP 协议开发的,在四层协议 TCP 协议的实现中也提供了 keepalive 报文用来探测对端是否可用。TCP 层将在定时时间到后发送相应的 KeepAlive 探针以确定连接可用性。

Netty 中提供了 tcp-keepalive 的设置:
在这里插入图片描述

.childOption(ChannelOption.SO_KEEPALIVE,true) 表示打开 TCP 的 keepAlive 设置。

2 Netty心跳检测机制

Netty 中提供了 IdleStateHandler 类专门用于处理心跳。构造函数如下:

public IdleStateHandler(long readerIdleTime, long writerIdleTime, long allIdleTime,TimeUnit unit) {
    this(false, readerIdleTime, writerIdleTime, allIdleTime, unit);
}

参数说明:

  • readerIdleTime 隔多久检查一下读事件是否发生,如果 channelRead() 方法超过 readerIdleTime 时间未被调用则会触发超时事件调用 userEventTrigger() 方法
  • writerIdleTime 隔多久检查一下写事件是否发生,如果 write() 方法超过 writerIdleTime 时间未被调用则会触发超时事件调用 userEventTrigger() 方法;
  • allIdleTime 隔多久检查读写事件是否发生
  • unit 时间单位

可以分别控制读,写,读写超时的时间,如果设置为0表示不检测,所以如果全是0,则相当于没添加这个 IdleStateHandler,连接是个普通的短连接。

2.1 代码演示

  • 服务端

    public class TestHeartServer {
    
        public static void main(String[] args) throws InterruptedException {
    
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
    
            try {
                ServerBootstrap bootstrap=new ServerBootstrap();
                bootstrap.group(bossGroup,workerGroup)
                          .channel(NioServerSocketChannel.class)
                          .option(ChannelOption.SO_BACKLOG,128)
                           .childOption(ChannelOption.SO_KEEPALIVE,true)
                           .handler(new LoggingHandler(LogLevel.INFO))//bossGroup处理handler
                           .childHandler(new ChannelInitializer<SocketChannel>() {//workergroup处理handler
                               @Override
                               protected void initChannel(SocketChannel ch) throws Exception {
                                   ChannelPipeline pipeline = ch.pipeline();
                                   //每隔5s检查一下是否有读事件发生
                                   pipeline.addLast(new IdleStateHandler(5,0,0, TimeUnit.SECONDS));
                                   pipeline.addLast(new TestHeartServerHandler());
                               }
                           });
                ChannelFuture channelFuture = bootstrap.bind(9999).sync();
                channelFuture.channel().closeFuture().sync();
    
            }finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    }
    
  • 服务端handler

    public class TestHeartServerHandler extends ChannelInboundHandlerAdapter {
    
    
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println("channelActive");
        }
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf buf=(ByteBuf) msg;
            System.out.println("客户端消息:"+buf.toString(StandardCharsets.UTF_8));
            //向客户端发送消息
            //ctx.writeAndFlush(Unpooled.copiedBuffer("heart",StandardCharsets.UTF_8));
        }
    
        /**
         *如果5s没有读请求,则向客户端发送心跳
         * @param ctx
         * @param evt
         * @throws Exception
         */
        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
            IdleStateEvent event = (IdleStateEvent) evt;
            switch (event.state()) {
                case READER_IDLE: //读空闲
                    //如果5s没有读请求,则向客户端发送心跳
                    ctx.writeAndFlush("server send Heartbeat").addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
                    break;
                case WRITER_IDLE://写空闲
                    break;
                case ALL_IDLE://读写空闲
                    break;
            }
        }
    }
    
  • 客户端

    public class TestHeartClient {
    
        public static void main(String[] args) {
    
            EventLoopGroup eventExecutors=new NioEventLoopGroup();
            try {
                Bootstrap bootstrap=new Bootstrap();
                bootstrap.group(eventExecutors)
                        .channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ChannelPipeline pipeline = ch.pipeline();
                                //每隔4s检查一下是否有写事件
                                pipeline.addLast(new IdleStateHandler(0,4,0, TimeUnit.SECONDS));
                                pipeline.addLast(new TestHeartClientHandler());
                            }
                        });
                ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9999).sync();
                //向服务端发送消息
                channelFuture.channel().writeAndFlush(Unpooled.copiedBuffer("Hello server, i'm online", StandardCharsets.UTF_8));
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                eventExecutors.shutdownGracefully();
            }
    
        }
    }
    
  • 客户端Handler

    public class TestHeartClientHandler extends ChannelInboundHandlerAdapter {
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf buf=(ByteBuf) msg;
            System.out.println("服务端发送的消息:"+buf.toString(StandardCharsets.UTF_8));
        }
    
        /**
         *
         * @param ctx
         * @param evt
         * @throws Exception
         */
        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
            IdleStateEvent event = (IdleStateEvent) evt;
            String eventType = null;
            switch (event.state()) {
                //读空闲
                case READER_IDLE:
                    break;
                case WRITER_IDLE://写空闲
                    //如果4s没有收到写请求,则向服务端发送心跳请求
                    ctx.writeAndFlush(Unpooled.copiedBuffer("client send Heartbeat",StandardCharsets.UTF_8)).addListener(ChannelFutureListener.CLOSE_ON_FAILURE) ;
                    break;
                case ALL_IDLE://读写空闲
                    break;
            }
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
             cause.printStackTrace();
             ctx.close();
        }
    }
    

解释一下代码的逻辑:

服务端添加了:

Copypipeline.addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));

每隔5s检查一下是否有读事件发生,如果没有就触发 handler 中的 userEventTriggered(ChannelHandlerContext ctx, Object evt)逻辑。

客户端添加了:

Copynew IdleStateHandler(0, 4, 0, TimeUnit.SECONDS)

每隔4s检查一下是否有写事件,如果没有就触发 handler 中的 userEventTriggered(ChannelHandlerContext ctx, Object evt)逻辑。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

warybee

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值