心跳检测是指在TCP长连接中,客户端和服务端定时发送和接受简单数据,确保服务正常,在Netty中,对心跳检测进行了很好的封装,下面我们来看一下心跳检测的实现和源码

Netty通过什么来实现心跳?

IdleStateHandler:Netty是通过IdleStateHandler来实现心跳检测的。

怎么使用?

客户端

public class HeartBeatClient {
    public static void main(String[] args) throws Exception {

        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group).channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            //加入解码器,接受新的信息时触发
                            pipeline.addLast(new StringDecoder());
                            //加入编码器,当发出信息时候进行编码后再发出
                            pipeline.addLast(new StringEncoder());
                            //加入客服端的业务处理器
                            pipeline.addLast(new HeartBeatClientHandler());
                        }
                    });

            System.out.println("netty client start。。");
            //同步连接服务端
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9000).sync();
            Channel channel = channelFuture.channel();
            while (channel.isActive()) {
                //每两秒发一次心跳检测
                Thread.sleep(2000);
//                每四秒进行一次心跳检测
//                Thread.sleep(4000);
                channel.writeAndFlush("心跳检测");
            }
        } finally {
            group.shutdownGracefully();
        }
    }
}

public class HeartBeatClientHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(" client received :" + msg);
        if (msg != null && msg.equals("服务端关闭连接")) {
            System.out.println(" 服务端关闭连接,客户端也关闭");
            ctx.channel().closeFuture();
        }
    }
}

服务端

public class HeartBeatServer {
    public static void main(String[] args) throws Exception {
        //接受连接线程池
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //处理业务线程池,默认是cpu凉杯
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            //加入解码器,接受新的信息时触发
                            pipeline.addLast(new StringDecoder());
                            //加入编码器,当发出信息时候进行编码后再发出
                            pipeline.addLast(new StringEncoder());
                            //加入心跳检测处理器
                            // readerIdleTime  读超时时间
                            // writerIdleTime  写超时时间
                            // allIdleTime     总的超时时间

                            pipeline.addLast(new IdleStateHandler(3, 0, 0, TimeUnit.SECONDS));
                            //加入服务端的业务处理器
                            pipeline.addLast(new HeartBeatServerHandler());
                        }
                    });

            System.out.println("netty server start。。");
            //
            ChannelFuture channelFuture = serverBootstrap.bind(9000).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

public class HeartBeatServerHandler extends SimpleChannelInboundHandler<String>  {
    int readIdleTimes = 0;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(" ====== > [server] message received : " + msg);
        if ("心跳检测".equals(msg)) {
            ctx.channel().writeAndFlush("Heath ok");
        } else {
            System.out.println(" 其他信息处理 ... ");
        }
    }

    /**
     * netty将超时的事务逻辑交由用户自己处理
     * @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:
                eventType = "读空闲";
                readIdleTimes++; // 读空闲的计数加1
                break;
            case WRITER_IDLE:
                eventType = "写空闲";
                // 不处理
                break;
            case ALL_IDLE:
                eventType = "读写空闲";
                // 不处理
                break;
        }
        System.out.println(ctx.channel().remoteAddress() + "超时事件:" + eventType);
        if (readIdleTimes > 3) {
            System.out.println(" [server]读空闲超过3次,关闭连接,释放更多资源");
            ctx.channel().writeAndFlush("服务端关闭连接");
            ctx.channel().close();
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.err.println("=== " + ctx.channel().remoteAddress() + " is active ===");
    }
}

IdleStateHandler源码

构造方法

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

构造方法没什么特别,主要是对其中一些属性赋值。

channelActive方法

@Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // This method will be invoked only if this handler was added
        // before channelActive() event is fired.  If a user adds this handler
        // after the channelActive() event, initialize() will be called by beforeAdd().
        initialize(ctx); //进入查看源码
        super.channelActive(ctx);
    }


channelActive中有个initialize(ctx)方法,看一下这个方法


private void initialize(ChannelHandlerContext ctx) {
        // Avoid the case where destroy() is called before scheduling timeouts.
        // See: https://github.com/netty/netty/issues/143
        switch (state) {
            case 1:
            case 2:
                return;
        }

        state = 1;
        initOutputChanged(ctx);

        lastReadTime = lastWriteTime = ticksInNanos();  //当前时间
        if (readerIdleTimeNanos > 0) { //读超时时间
            readerIdleTimeout = schedule(ctx, new ReaderIdleTimeoutTask(ctx),
                    readerIdleTimeNanos, TimeUnit.NANOSECONDS);
        }
        if (writerIdleTimeNanos > 0) {
            writerIdleTimeout = schedule(ctx, new WriterIdleTimeoutTask(ctx),
                    writerIdleTimeNanos, TimeUnit.NANOSECONDS);
        }
        if (allIdleTimeNanos > 0) {
            allIdleTimeout = schedule(ctx, new AllIdleTimeoutTask(ctx),
                    allIdleTimeNanos, TimeUnit.NANOSECONDS);
        }
    }

initialize(ctx)方法主要是对一些属性赋值和判断是否要进行读,写等超时的定时方法,因为我们是测试读超时,那么我们看一下ReaderIdleTimeoutTask的run方法

@Override
        protected void run(ChannelHandlerContext ctx) {
            long nextDelay = readerIdleTimeNanos;
            if (!reading) { //获取是否超时 nextDelay<0 超时 nextDelay>0则不超时
                nextDelay -= ticksInNanos() - lastReadTime;
            }

            if (nextDelay <= 0) {
                // Reader is idle - set a new timeout and notify the callback.
                readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS);

                boolean first = firstReaderIdleEvent;
                firstReaderIdleEvent = false;

                try {
                    IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
                    channelIdle(ctx, event); //交给自己实现的userEventTriggered进行处理
                } catch (Throwable t) {
                    ctx.fireExceptionCaught(t);
                }
            } else {
                // Read occurred before the timeout - set a new timeout with shorter delay. 没超时,则再次调用schedule()方法
                readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
            }
        }

这里首先会判断是否以前超过了预订的超时时间,若没超时,则将赋值剩余超时时间,继续调用本方法,若超时,则调用我们自己实现的userEventTriggered()方法处理业务逻辑

流程图

java 服务 健康心跳检测 netty心跳检测_服务端