Netty ChannelHandler 生命周期
1. 前言
本节内容,我们主要讲解 ChannelHandler 在执行过程中的生命周期是什么样的?需要执行哪些核心的生命周期方法以及顺序?
了解生命周期的核心目的是,可以在合适的生命周期方法扩展自己的业务功能。
2. UML 关系
首先,我们先来了解以下 ChannelHandler 的类依赖关系图,具体如下所示:
通过上面的类结构图,我们总结一下规律:
- ChannelHandler 有两个子接口,分别是
ChannelInboundHandler
和ChannelOutboundHandler
,其实从字面意思就能知道,它们分别是入站和出站的接口类。 - 如果我们自定义的业务 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 生命周期总结
我们来逐个总结一下每个回调方法的含义
- handlerAdded () :当检测到新连接之后,调用
ch.pipeline().addLast(new LifeCycleHandler());
之后的回调,表示在当前的 channel 中,已经成功添加了一个 handler 到双向链表。 - channelRegistered ():这个回调方法,表示当前的 channel 的所有的逻辑处理已经和某个 NIO 线程建立了绑定关系,从线程池里面去抓一个线程绑定在这个 channel 上,这里的 NIO 线程通常指的是 NioEventLoop。
- channelActive ():当 channel 的所有的业务逻辑链准备完毕,channel 的 pipeline 中已经添加完所有的 handler 以及绑定好一个 NIO 线程之后,这条连接算是真正激活了,接下来就会回调到此方法。
- channelRead ():客户端向服务端发来数据,每次都会回调此方法,表示有数据可读。
- channelReadComplete ():服务端每次读完一次完整的数据之后,回调该方法,表示数据读取完毕。
- channelInactive (): 表面这条连接已经被关闭了,这条连接在 TCP 层面已经不再是 ESTABLISH 状态了。
- channelUnregistered (): 既然连接已经被关闭,那么与这条连接绑定的线程就不需要对这条连接负责了,这个回调就表明与这条连接对应的 NIO 线程移除掉对这条连接的处理。
- handlerRemoved ():给这条连接上添加的所有的业务逻辑处理器都给移除掉。
ChannelHandler 回调方法的执行顺序为
- 连接请求,handlerAdded () -> channelRegistered () -> channelActive () -> channelRead () -> channelReadComplete ();
- 数据请求,channelRead () -> channelReadComplete ();
- 通道被关闭,channelInactive () -> channelUnregistered () -> handlerRemoved ()。
5. 小结
本节内容主要讲解 ChannelHandler 的生命周期方法的执行顺序及触发机制,目的是了解每个方法的触发时间点,有助于业务点的扩展。核心掌握以下知识点:
- 核心的生命周期方法有哪些,它们的触发时间点是什么;
- channelRegistered 需要清楚,这个不容易理解;
- channelRead 和 channelReadComplete 的区别,需要清楚;
- 通过三种 Demo 来说明了不同的生命周期方法的执行次数,有的是只执行一次,有的是每次都会执行。