前言
上篇讲解ChannelHandler的使用以及相关子类的使用,这一篇继续来讲讲ChannelPipeline与ChannelHandlerContext,它们三者的有效组合让Netty在处理业务逻辑上得心应手。在讲ChannelHandlerContext之前先来讲讲ChannelPipeline。
ChannalPipeline
Pipeline 可以翻译成管道,在Netty中ChannelPipeline的作用也如同一条管道,通过这条管道处理入站与出站的数据、事件;通过ChannelPipeline对ChannelHandler的有效编排和交互组成一个应用程序数据和事件处理逻辑的核心。
Netty会对每一个新创建的Channel分配一个与之关联的新的ChannelPipeline实例,这个关联是永久的并且是一一对应的,一个Channel不能附加另外一个ChannelPipeline实例也不能分离当前关联的ChannelPipeline实例。下面通过一个示例来看看:
1 | bootstrap.childHandler(new ChannelInitializer<SocketChannel>() { |
ChannelInitializer是一个ChannelHandler类型,作用是有新的连接进来后通过ChannelHandler的handlerAdded事件完成Channel中的ChannelPipeline的初始编排工作,handlerAdded事件会委托给initChannel方法执行,也就是上面例子中看到的,在initChannel方法中可以通过参数获取到当前新连接并创建关联的Channel实例,通过Channal实例的pipeline()方法获取到一一对应的ChannelPipeline实例,再通过addList方法加入ChannelHadler类型的实例变量,在执行完initChannel方法后ChannelInitializer会将自己从当前的Channel关联的ChannelPipeline中移除。
ChannalPipeline可以用于编排ChannelHandler的方法列表:
| 方法 | 描述 |
|---|---|
| addFirst addBefore addAfter addLast |
将一个ChannelHandler添加到ChannelPipeline中 |
| remove | 将一个ChannelHandler从ChannelPipeline中移除 |
| replace | 将ChannelPipeline中的一个ChannelHandler进行替换, 替换成另一个ChannelHandler |
阅读过前面一部分文章的同学会知道Netty对ChannelHandler又做了区分,分为入站与出站,如下图:

通常入站与出站处理器在编排到Pipeline中时会是混合着的,这样的情况Pipeline能正确的处理入站与出站吗?ChannelPipeline可以通过本身传播事件方法,如果一个入站事件被触发,它将从Pipeline的头部开始一直向后传递直到尾端,这里需要通过ChannelPipeline的相对论来讨论事件传播的正确性问题;Netty总是将入站口作为头部,对出站口作为尾部(内部只有一条,使用链表存储所有处理器)。当我们使用ChannelPipeline.add*()的方法将入站与出站处理器混合添加到Pipeline之后,在Pipeline中将会按添加的顺序进行排列,就如上图中每个处理器上的编号一样。ChannelPipeline在传播事件时会测试Pipeline中一个ChannelHandler的类型是否和事件运动的方向相匹配,如果不匹配将跳过该ChannelHandler将继续向前,直接到尾端(这里就体现了ChannelInboundHandler与ChannelOutboundHandler的重要性,ChannelPipeline在传播时判断下一个能ChannelHandler的功能是通过ChannelHandlerMask类实现,篇幅问题就放到以后的源码分析时再讲解吧)。所以在ChannelPipeline中虽然只有使用一条链表来保存所有的ChannelHandler,但通过事件传播方向的检测将一条逻辑上拆分成两条从而实现了对于入站与出站两条链路上分别有各自的头部与尾部。
ChannelPipeline中使用链表来存储ChannelHandler,是怎样存储的呢?
ChannelHandlerContext
调用ChannelPipeline.add*()方法添加一个ChannelHandler至当前Pipeline中时都会创建一个ChannelHandlerContext实例,将添加的ChannelHandler包装在ChannelHandlerContext实例中。而ChannelPipeline链表上的节点类型就是ChannelHandlerContext。上面说到ChannilPipeline是可以进行事件传播的,这是因为其继承了ChannelInboundInvoker与ChannelOutboundInvoker,这两个Invoker接口定义了入站与出站各方向上的事件所对应的方法(这两个接口在后续讲解Netty的源码时再深入导论,这里就暂且放过,只要知道就好了);ChannelHanderContext也继承了这两个Invoker接口类,这也就是说通过ChannelHanderContext实现也可以触发事件的传播,下面来看个例子:
1 | public static class MessageChannel extends ChannelInboundHandlerAdapter { |
ChannelHandlerContext.pipeline()获取到的是当前Channel所关联的pipeline,上面也提到过通过pipeline传播事件时会从头部开始,当你自己尝试使用上面的示例运行时得到的结果将会是一个死循环;而通过ChannelHandlerContext.fireChannelRead传播事件将会又下一个ChannelHandler开始传播,当重写ChannelInboundHandlerAdapter实现业务逻辑时让事件传播下去就需要显示调用ChannelHandlerContext.fireChannelRead或者调用super.fireChannelRead方法,不然事件在当前ChannelHandler就会结束传播。
对事件传播的两种不同方式特点说明:
- ChannelPipeline的事件传播是通过将事件方法的执行委托给链表中的ChannelHandlerContext执行(ChannelPipeline与ChannelHandlerContext都实现了
ChannelInboundInvoker与ChannelOutboundInvoker) - 调用
ChannelPipeline.fireChannelRead传播事件将从链表的头部开始触发(是链表的头部,不是事件传播方向上的,但传播过程中会根据事件的类型判断方向) - ChannelHandlerContext作为Pipeline链表的节点类型承担了事件传播的责任,Pipeline的责任只是编排,而传播与承载的主要角色是ChannelHandlerContext
通过这个说明应该能理解在事件传播是怎么进行的了,通过ctx.pipeline().fireChannelRead传播事件和通过ctx.fireChannelRead传播事件的区别与结果也不难理解了。
类关系图
最后通过一张类关系图来回顾下上面本篇的知识点吧。

ChannelPipeline与ChannelHandlerContext都继承自ChannelInboundInvoker与ChannelOutbountInvoker以实现事件的传播功能;ChannelHandlerContext包装了ChannelHandler实例,负责事件传播过程中调用具体的ChannelHandler以实现应用程序功能,ChannelPipeline通过链表存储ChannelHandlerContext,负责事件的源的调用,将事件执行委托给ChannelHandlerContext实际执行。
执行流程如下:
