【Netty】进阶篇一:心跳检测机制

1 案例要求

  • 编写一个 Netty心跳检测机制案例:
    • 当服务器超过3秒没有读时,就提示读空闲;
    • 当服务器超过5秒没有写操作时,就提示写空闲;
    • 实现当服务器超过7秒没有读或者写操作时,就提示读写空闲;

2 代码实现分析

2.1 代码实现

public class MyServer {
    
    
    public static void main(String[] args) {
    
    
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup(8);
        try {
    
    
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))// 日志处理器
                    .childHandler(new ChannelInitializer<SocketChannel>() {
    
    
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
    
    
                            ChannelPipeline pipeline = ch.pipeline();
                            // 加入 netty 提供的 IdleStateHandler
                            /*
                            1.IdleStateHandler 是 netty 提供的处理空闲状态的处理器
                            2.参数说明:
                                long readerIdleTime:表示服务端多长时间没有读数据,就会发送一个心跳检测包检测是否连接
                                long writerIdleTime:表示服务端多长时间没有写数据,就会发送一个心跳检测包检测是否连接
                                long allIdleTime:表示服务端多长时间没有 读/写 数据,就会发送一个心跳检测包检测是否连接
                                TimeUnit unit:时间单位
                            3.当 IdleStateEvent 触发后,就会传递给管道的下一个handler去处理,
                              通过调用(触发)handler 的 userEventTiggered,在该方法中去处理
                              IdleStateEvent(读空闲、写空闲、读写空闲)
                             */
                            pipeline.addLast(new IdleStateHandler(3,5,7, TimeUnit.SECONDS));
                            // 加入一个对空闲检测进一步处理的 handler(自定义)
                            // 实现 userEventTiggered() 方法
                            pipeline.addLast(new MyServerHandler());

                        }
                    });
            System.out.println("netty 服务器启动成功");
            ChannelFuture channelFuture = bootstrap.bind(7000).sync();

            // 监听关闭事件
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
// ================================================================ // 
public class MyServerHandler extends ChannelInboundHandlerAdapter {
    
    

    /**
     *
     * @param ctx 上下文
     * @param evt 事件
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    
    
        if (evt instanceof IdleStateEvent) {
    
    
            IdleStateEvent event = (IdleStateEvent) evt;
            String eventType = null;
            switch (event.state()) {
    
    
                case READER_IDLE:
                    eventType = "读空闲";
                    break;
                case WRITER_IDLE:
                    eventType = "写空闲";
                    break;
                case ALL_IDLE:
                    eventType = "读写空闲";
                    break;
            }
            System.out.println(ctx.channel().remoteAddress() + " 发生了超时事件:" + eventType);
        }

    }
}

上面的代码和前面几篇文章的总体实现思路基本一致,只是在 bootstrap中加入了 Netty 自带的 日志处理器LoggingHandler,并且在pipline中加入了Netty 自带的 IdleStateHandler
在这里插入图片描述

2.2 代码分析

我们重点分析一下IdleStateHandler类,IdleStateHandler类 是 netty 提供的处理空闲状态的处理器。

首先我们看一下官方文档中对它的描述:Triggers an {@link IdleStateEvent} when a {@link Channel} has not performed read, write, or both operation for a while.翻译过来就是:当 {@link Channel} 有一段时间没有执行读取、写入或读写操作时,触发 {@link IdleStateEvent}。

我们在pipline添加它时配置了四个参数:

  • long readerIdleTime:表示服务端多长时间没有读数据,就会发送一个心跳检测包检测是否连接
  • long writerIdleTime:表示服务端多长时间没有写数据,就会发送一个心跳检测包检测是否连接
  • long allIdleTime:表示服务端多长时间没有 读/写 数据,就会发送一个心跳检测包检测是否连接
  • TimeUnit unit:时间单位
    pipeline.addLast(new IdleStateHandler(3,5,7, TimeUnit.SECONDS));
    

根据官方文档的描述,我们可以知道当 Channel 有一段时间没有执行读取、写入或读写操作时,就会触发 IdleStateEventIdleStateEvent 触发后,就会传递给管道的下一个handler去处理(就是上面代码中自己写的 handler),通过调用(触发) handler 中的的 userEventTiggered()方法,在该方法中去处理IdleStateEvent(读空闲、写空闲、读写空闲)。

@Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
    
    
        if (evt instanceof IdleStateEvent) {
    
    
            IdleStateEvent event = (IdleStateEvent) evt;
            String eventType = null;
            switch (event.state()) {
    
    
                case READER_IDLE:
                    eventType = "读空闲";
                    break;
                case WRITER_IDLE:
                    eventType = "写空闲";
                    break;
                case ALL_IDLE:
                    eventType = "读写空闲";
                    break;
            }
            System.out.println(ctx.channel().remoteAddress() + " 发生了超时事件:" + eventType);
        }

    }

我们可以看见该方法有一个 Objec evt 参数,它的类型是 IdleStateEvent。
在这里插入图片描述
我们能通过 IdleStateEvent 类中的 IdleState state 属性得知发生的是什么事件(读空闲、写空闲、读写空闲)。IdleState 是一个枚举类。

/**
 * An {@link Enum} that represents the idle state of a {@link Channel}.
 */
public enum IdleState {
    
    
    /**
     * No data was received for a while.
     */
    READER_IDLE,
    /**
     * No data was sent for a while.
     */
    WRITER_IDLE,
    /**
     * No data was either received or sent for a while.
     */
    ALL_IDLE
}

3 问题思考

之前我们自定义handler的时候会继承 SimpleChannelInboundHandler,它的父类有handlerRemoved()方法,该方法会在channel断开连接的时候调用,那么有这个方法为什么还需要心跳检测呢?直接在channel断开的时候做处理不就行了吗?

public abstract class ChannelHandlerAdapter implements ChannelHandler {
    
    

	@Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    
    
        // NOOP
    }
}

这是因为很多情况服务端感知不到channel断开连接,比如手机突然强制关机、进入飞行模式等情况,这样的话TCP连接没有经过四次挥手断开连接,因此服务端无法感知,还是需要心跳检测机制来确保客户端是否在线。

猜你喜欢

转载自blog.csdn.net/qq_36389060/article/details/124567222