netty(十五)源码分析之ChannnelPipeline

来源:互联网 发布:马斯克 人工智能 编辑:程序博客网 时间:2024/06/11 20:38
Netty的Channel过滤器实现原理余ServletFilter机制一致,它将Channnel的数据管道抽象为ChannelPipeline,消息在ChannelPipeline中流动和传递。ChannelPipeline持有I/O事件拦截器ChannelHandler的链表,由ChannelHandler对I/O事件进行拦截和处理,可以方便地通过新增和删除CHannelHandler来实现不同的业务逻辑定制,不需要对已有的ChannelHandler进行修改,能够实现对修改封闭和对扩展的支持。

下面我们对ChannelPipeline和ChannelHandler,以及与之相关的ChannelHandlerContext进行详细介绍和源码分析。

ChannelPipeline功能说明

ChannelPipeline是ChannelHandler的容器,它负责ChannelHandler的管理和事件拦截与调度。

ChannelPipeline的事件处理
下图展示了一个消息被ChannelPipeline的ChannelHandler链拦截和处理的全过程,消息的读取和发送处理全流程描述如下:
(1)底层的SocketChannel read()方法读取ByteBuf,触发ChannelRead事件,由I/O线程NioEventLoop调用ChannelPipeline的fireChannelRead(Object msg)方法,将消息(ByteBuf)传输到ChannelPipeline中。

(2)消息依次被HeadHandler,ChannelHandler1,ChannelHandler2... ...TailHandler拦截和处理,在这个过程中,任何ChannelHandler都可以终端当前的流程,结束消息的传递。

(3)调用ChannelHandlerContext的write方法,发送消息,消息从TailHandler开始,途径ChannelHandlerN......channelHandler1,HeadHandler,最终被添加到消息发送缓冲区中等待刷新和发送,在此过程中也可以中断消息的传递,例如当编码失败时,就需要中断流程,构造异常的Future返回。

Netty中的事件分为inbound事件和outbound事件。inbound事件通常由I/O线程触发,例如TCP链路建立事件,链路关闭事件,读事件,异常通知事件。inbound事件通常由I/O线程触发,例如TCP链路建立事件,链路关闭事件,读事件,异常通知事件等。
触发inbound事件的方法如下。
(1)ChannelHandlerContext.fireChannelRegistered():Channel注册事件;
(2)ChannelHandlerContext.fireChannelActive():TCP链路建立成功,Channel激活事件;
(3)ChannelHandlerContext.fireChannelRead(Object):读事件;
(4)ChannelHandlerContext.fireChannelReadComplete():读操作完成通知事件;
(5)ChannelHandlerContext.fireExceptionCaught(Throwable):异常通知事件;
(6)ChannelHandlerContext.fireUserEventTriggered(Object):用户自定义事件;
(7)ChannelHandlerContext.fireChannelWritabilityChanged():Channel的可写状态变化通知事件;
(8)ChannelHandlerContext.fireChannelInactive():TCP连接关闭,链路不可用通知事件。

Outbound事件通常是由用户主动发起的网络I/O操作,例如用户发起的连接操作,绑定操作,消息发送等操作,对应上图的右半部分。
触发outbound事件的方法如下:
(1)ChannelHandlerContext.bind(SocketAddress,ChannelPromise):绑定本地地址事件;
(2)ChannelHandlerContext.connect(SocketAddress,SocketAddress,ChannelPromise):连接服务端事件;
(3)ChannelHandlerContext.write(Object,ChannelPipeline):发送事件;
(4)ChannelHandlerContext.flush():刷新事件;
(5)ChannelHandlerContext.read():读事件;
(6)ChannelHandlerContext.disconnect(ChannelPromise):断开连接事件;
(7)ChannelHandlerContext.close(ChannelPromise):关闭当前Channel事件。

自定义拦截器

ChannelPipeline通过ChannelHandler接口来实现事件的拦截和处理,由于ChannelHandler中的事件种类繁多,不同的ChannelHandler可能只需要关心其中的某一个或者几个事件,所以,通常ChannelHandler只需要继承ChannelHandlerAdapte类覆盖自己关心的方法即可。
例如,下面的例子展示了拦截Channel Active事件,打印TCP链路建立成功日志,代码如下。

public class MyInboundHandler extends ChannelHandlerAdapter{    @Overridepublic void channelActive(ChannelHandlerContext ctx){     System.out.println("TCP connected!"); ctx.fireChannelActive();}}
下面的例子展示了如何在链路关闭的时候释放资源,示例代码如下。
public class MyOutboundHandler extends ChannelHandlerAdapter{@Overridepublic void close(ChannelHandlerContext ctx,ChannelPromise promise){System.out.println("TCP closing ..");ctx.close(promise);}}
对于类似编解码这样的ChannelHandler,它存在先后顺序,例如MessageTOMessageDecoder,在它之前往往需要有ByteToMessageDecoder将ByteBuf解码为对象,然后对对象做二次解码得到最终的POJO对象。Pipeline支持指定位置添加或者删除拦截器,相关接口定义如下:

ChannelPipeline的主要特性

ChannelPipeline支持运行态动态的添加或者删除ChannelHandler,在某些场景下这个特性非常有用。例如当业务高峰期需要对系统做拥塞保护时,就可以根据当前的系统时间进行判断,如果处于业务高峰期,则动态地将系统拥塞保护ChannelHandler添加到当前的ChannelPipeline中,在高峰期过去后,就可以动态删除拥塞保护ChannelHandler了。

ChannelPipeline是线程安全的,这意味着N个业务线程可以并发地操作ChannelPipeline而不存在多线程并发问题。但是,ChannelHandler却不是线程安全的,这意味着尽管CHannelPipeline是线程安全的,但是用户仍然需要自己保证ChannelHandler的线程安全。

ChannelPipeline源码分析

ChannelPipeline的代码相对比较简单,它实际上是一个ChannelHandler容器,内部维护了一个ChannelHandler的链表和迭代器,可以方便地实现ChannelHandler查找,添加,替换和删除。

ChannelPipeline的类继承关系图

ChannelPipeline对ChannelHandler的管理

ChannelPipeline是ChannelHandler的管理容器,负责ChannelHandler的查询,添加,替换和删除。

ChannelPipeline的inbound事件

当发生某个I/O事件的时候,例如链路建立,链路关闭,读取操作完成等,都会产生一个事件,时间在Pipeline中得到传播和处理,它是事件处理的总入口。由于网络I/O相关的事件有限,因此Netty对这些事件进行了统一抽象,Netty自身和用户的ChannelHandler会对感兴趣的事件进行拦截和处理。
pipeline中以fireXXX命名的方法都是从I/O线程流向用户业务Handler的inbound事件,他们的实现因功能而异,但是处理步骤类似,总结如下。
(1)调用HeadHandler对应的fireXXX方法;
(2)执行事件相关的逻辑操作。

以fireChannelActive方法为例

    public ChannelPipeline fireChannelActive() {        this.head.fireChannelActive();        if(this.channel.config().isAutoRead()) {            this.channel.read();        }        return this;    }

ChannelPipeline的outbound事件

由用户线程或者代码发起的I/O操作被称为outbound事件,事实上inbound和outbound是Netty自身根据事件在pipeline中的流向抽象出来的术语,在其他NIO框架中没有这个概念。
inbound事件相关联的操作如下所示:

Pipeline本身并不直接进行I/O操作,在前面对Channel和Unsafe的介绍中我们知道最终都是由unsafe和Channel来实现真正的I/O操作的。Pipeline负责将I/O事件通过TailHandler进行调度和传播,最终调用Unsafe的I/O方法进行I/O操作,相关代码实现如下:

    @Override    public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {        return tail.connect(remoteAddress, localAddress);    }

它直接调用TailHandler的connect方法,最终会调用到HeadHandler的connect方法:

        @Override        public void connect(                ChannelHandlerContext ctx,                SocketAddress remoteAddress, SocketAddress localAddress,                ChannelPromise promise) throws Exception {            unsafe.connect(remoteAddress, localAddress, promise);        }

最终由Headhandler调用Unsafe的connect方法发起真正的连接,pipeline仅仅负责事件的调度。

ChannelHandler功能说明

ChannelHandler类似于Servlet的Filter过滤器,负责对I/O事件或者I/O操作进行拦截和处理,它可以选择性地拦截和处理自己感兴趣的事件也可以透传和终止事件的传递。
基于ChannelHandler接口,用户可以方便地进行业务逻辑定制,例如打印日志,统一封装异常信息,性能统计和消息编解码等。
ChannelHandler支持注解,目前支持的注解有两种。
  • Sharable:多个ChannelPipeline共用同一个ChannelHandler;
  • Skip:被Skip注解的方法不会被调用,直接被忽略。
ChannelHandlerAdapter功能说明

对于大多数的ChannelHandler会选择性地拦截和处理某个或者某些事件,其他的事件会忽略,由下一个ChannelHandler进行拦截和处理。这就会导致一个问题:用户ChannelHandler必须要实现ChannelHandler的所有接口,包括它不关心的那些事件处理接口,这会导致用户代码的冗余和臃肿,代码的可维护性也会变差。
为了解决这个问题,Netty提供了ChannelHandlerAdapter基类,它的所有接口实现都是事件透传,如果用户ChannelHandler关心某个事件,只需要覆盖ChannelHandlerAdapter对应的方法即可,对于不关心的,可以直接继承使用父类的方法,这样子类的代码就会简洁和清晰。