Netty ChannelHandler 生命周期

1. 前言

本节内容,我们主要讲解 ChannelHandler 在执行过程中的生命周期是什么样的?需要执行哪些核心的生命周期方法以及顺序?

了解生命周期的核心目的是,可以在合适的生命周期方法扩展自己的业务功能。

2. UML 关系

首先,我们先来了解以下 ChannelHandler 的类依赖关系图,具体如下所示:
图片描述

通过上面的类结构图,我们总结一下规律:

  1. ChannelHandler 有两个子接口,分别是 ChannelInboundHandlerChannelOutboundHandler,其实从字面意思就能知道,它们分别是入站和出站的接口类。
  2. 如果我们自定义的业务 Handler 直接实现 ChannelInboundHandler 或者 ChannelOutboundHandler,那么我们需要实现的接口非常的多,增加了开发的难度。Netty 已经帮我们封装好了两个实现类,分别是 ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter,这样可以大大简化了开发工作。

3. 核心生命周期方法

方法 描述
handlerAdded Handler 被加入 Pipeline 时触发(仅仅触发一次)
channelRegistered channelRegistered 注册成功时触发
channelActive channel 连接就绪时触发
channelRead channel 有数据可读时触发
channelReadComplete channel 有数据可读,并且读完时触发
channelInactive channel 断开时触发
channelUnregistered channel 取消注册时触发
handlerRemoved handler 被从 Pipeline 移除时触发

问题 1:channelRegistered 注册指的是什么呢?

Channel 在创建时,需要绑定 ChannelPipeline 和 EventLoop 等操作,完成这些操作时会触发 channelRegistered () 方法。

问题 2:channelRead 和 channelReadComplete 的区别?

当 Channel 有数据可读时,会触发 channelRead 事件,eventLoop 被唤醒并且调用 channelRead () 处理数据;eventLoop 唤醒后读取数据包装成 msg,然后将 msg 作为参数调用 channelRead (),期间做了个判断,读取到 0 字节或者读取到的字节数小于 buffer 的容量,满足以上条件就会调用 channelReadComplete ()。

4. 生命周期执行流程

ChannelHandler 的一些特殊回调方法,这些回调方法的执行是有顺序的,而这个执行顺序可以称为 ChannelHandler 的生命周期。

4.1 客户端发送一次请求

public class InboundHandler1 extends ChannelInboundHandlerAdapter {
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerAdded");
        super.handlerAdded(ctx);
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelRegistered");
        super.channelRegistered(ctx);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelActive");
        super.channelActive(ctx);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("channelRead");
        super.channelRead(ctx, msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelReadComplete");
        super.channelReadComplete(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelInactive");
        super.channelInactive(ctx);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelUnregistered");
        super.channelUnregistered(ctx);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerRemoved");
        super.handlerRemoved(ctx);
    }
}

执行结果:

handlerAdded
channelRegistered
channelActive
channelRead
channelReadComplete

4.2 客户端发送多次请求

客户端每隔 5 秒钟发送一次消息给服务端,查看效果如何?

实例:

final ChannelFuture channelFuture=bootstrap.connect("127.0.0.1",80).sync();
channelFuture.addListener(new ChannelFutureListener() {
    public void operationComplete(ChannelFuture future) throws Exception {
        if(future.isDone()){
            if(future.isSuccess()){
                
                //1秒钟之后,每隔5描述发送一次消息
                channelFuture.channel().eventLoop().scheduleWithFixedDelay(new Runnable() {
                    public void run() {
                        channelFuture.channel().writeAndFlush("hello world");
                    }
                },1,5, TimeUnit.SECONDS);

            }else if(future.isCancelled()){
                System.out.println("连接被取消");
            }else if(future.cause()!=null){
                System.out.println("连接出错:");
            }
        }
    }
});

执行结果:

handlerAdded
channelRegistered
channelActive
channelRead
channelReadComplete
    
channelRead
channelReadComplete
    
channelRead
channelReadComplete

通过执行结果我们发现,第一次的时候执行 handlerAdded ()、channelRegistered ()、channelActive (),后面就不会被执行了。

客户端的每次请求时,都会触发 channelRead () 和 channelReadComplete () 两个核心方法。

4.3 手工关闭通道

疑问:channelInactive、channelUnregistered、handlerRemoved 什么时候会被执行呢?

实例:

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    System.out.println("channelRead");
    //手工关闭通道
    ctx.channel().close();
}

执行结果:

handlerAdded
channelRegistered
channelActive
channelRead
channelReadComplete
channelInactive
channelUnregistered
handlerRemoved

总结,人为的关闭通道或者其他因素(比如:网络故障等),则会触发 channelInactive、channelUnregistered、handlerRemoved 的执行。

4.4 生命周期总结

我们来逐个总结一下每个回调方法的含义

  1. handlerAdded () :当检测到新连接之后,调用 ch.pipeline().addLast(new LifeCycleHandler()); 之后的回调,表示在当前的 channel 中,已经成功添加了一个 handler 到双向链表。
  2. channelRegistered ():这个回调方法,表示当前的 channel 的所有的逻辑处理已经和某个 NIO 线程建立了绑定关系,从线程池里面去抓一个线程绑定在这个 channel 上,这里的 NIO 线程通常指的是 NioEventLoop。
  3. channelActive ():当 channel 的所有的业务逻辑链准备完毕,channel 的 pipeline 中已经添加完所有的 handler 以及绑定好一个 NIO 线程之后,这条连接算是真正激活了,接下来就会回调到此方法。
  4. channelRead ():客户端向服务端发来数据,每次都会回调此方法,表示有数据可读。
  5. channelReadComplete ():服务端每次读完一次完整的数据之后,回调该方法,表示数据读取完毕。
  6. channelInactive (): 表面这条连接已经被关闭了,这条连接在 TCP 层面已经不再是 ESTABLISH 状态了。
  7. channelUnregistered (): 既然连接已经被关闭,那么与这条连接绑定的线程就不需要对这条连接负责了,这个回调就表明与这条连接对应的 NIO 线程移除掉对这条连接的处理。
  8. handlerRemoved ():给这条连接上添加的所有的业务逻辑处理器都给移除掉。

ChannelHandler 回调方法的执行顺序为

  1. 连接请求,handlerAdded () -> channelRegistered () -> channelActive () -> channelRead () -> channelReadComplete ();
  2. 数据请求,channelRead () -> channelReadComplete ();
  3. 通道被关闭,channelInactive () -> channelUnregistered () -> handlerRemoved ()。

图片描述

5. 小结

本节内容主要讲解 ChannelHandler 的生命周期方法的执行顺序及触发机制,目的是了解每个方法的触发时间点,有助于业务点的扩展。核心掌握以下知识点:

  1. 核心的生命周期方法有哪些,它们的触发时间点是什么;
  2. channelRegistered 需要清楚,这个不容易理解;
  3. channelRead 和 channelReadComplete 的区别,需要清楚;
  4. 通过三种 Demo 来说明了不同的生命周期方法的执行次数,有的是只执行一次,有的是每次都会执行。