讲讲 Netty 中ChannelPipeline 和 ChannelHandler 的关系
在 Netty 中,ChannelPipeline 和 ChannelHandler 的关系是“流水线”与“流水线工人”的关系。 它们共同构成了 Netty 处理网络事件(I/O 操作、数据编解码、业务逻辑等)的核心机制,本质上是拦截过滤器模式(Intercepting Filter Pattern)的具体实现。
为了让你彻底弄懂它们的关系,我们可以从以下几个维度来拆解:
1. 核心比喻:工厂流水线
Channel(网络连接):好比工厂里运送物资的传送带。ChannelPipeline(管道):好比传送带上的整条组装流水线。每个Channel在创建时,都会自动且唯一地绑定一个ChannelPipeline。ChannelHandler(处理器):好比流水线上的一个个工人。每个工人只负责自己的一道工序(比如:工人A负责拆包,工人B负责把数据转成对象,工人C负责具体的业务计算)。
2. ChannelPipeline 的本质:双向链表
ChannelPipeline 实际上是一个双向链表。它负责存放和管理所有的 ChannelHandler,并控制网络事件在这个链表中的流转。
当一个事件(如接收到数据、连接建立、发生异常)产生时,ChannelPipeline 会按照一定的顺序,把事件传递给链表里的下一个 ChannelHandler 去处理。
默认情况下,ChannelPipeline 包含两个特殊的节点:
- Head 节点:链表的头部,负责与底层的 Socket 交互。
- Tail 节点:链表的尾部,负责做一些默认的处理(比如释放未被处理的内存,防止内存泄漏)。
3. ChannelHandler 的分类:入站与出站
流水线上的“工人”分为两类,专门处理不同方向的事件:
ChannelInboundHandler(入站处理器):处理从外部进入系统的事件。例如:接收到对端的数据(Read)、连接被激活(Active)。- 执行顺序:从 Head 向 Tail 方向依次执行。(顺着链表走)
ChannelOutboundHandler(出站处理器):处理从系统发往外部的事件。例如:向对端写数据(Write)、主动发起连接(Connect)。- 执行顺序:从 Tail 向 Head 方向依次执行。(逆着链表走)
示意图:
plaintext
I/O Request
via Channel or
ChannelHandlerContext
|
+---------------------------------------------------+---------------+
| ChannelPipeline | |
| \|/ |
| +---------------------+ +-----------+----------+ |
| | Inbound Handler N | | Outbound Handler 1 | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
| | \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler N-1 | | Outbound Handler 2 | |
| +----------+----------+ +-----------+----------+ |
| /|\ . |
| . . |
| ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
| [ method call] [method call] |
| . . |
| . \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler 2 | | Outbound Handler M-1 | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
| | \|/ |
| +----------+----------+ +-----------+----------+ |
| | Inbound Handler 1 | | Outbound Handler M | |
| +----------+----------+ +-----------+----------+ |
| /|\ | |
+---------------+-----------------------------------+---------------+
| \|/
+---------------+-----------------------------------+---------------+
| | | |
| [ Socket.read() ] [ Socket.write() ] |
| |
| Netty Internal I/O Threads (Transport Implementation) |
+-------------------------------------------------------------------+
4. 关键的纽带:ChannelHandlerContext
这三者之间还有一个非常重要的角色:ChannelHandlerContext(上下文)。
ChannelHandler本身只是一个处理逻辑的类,它并不知道自己在ChannelPipeline的什么位置,也不知道前面和后面的 Handler 是谁。- 当把一个
Handler添加到Pipeline时,Netty 会为这个Handler包装一个ChannelHandlerContext。 - 实际上,
ChannelPipeline这个双向链表里连接的节点是ChannelHandlerContext,而不是直接连接ChannelHandler。 - 作用:
ChannelHandler通过它所属的Context来与Pipeline进行交互。比如调用ctx.fireChannelRead()把数据传给下一个入站处理器,或者调用ctx.write()把数据传给上一个出站处理器。
5. 一个极其重要的细节:ctx.write() vs channel.write()
理解了 Pipeline 和 Handler 的关系,就能明白 Netty 中一个经典的面试题:在 Handler 中写数据时,用 ctx.write() 和 ctx.channel().write() 有什么区别?
假设你的 Pipeline 顺序是:Head -> Inbound A -> Outbound B -> Outbound C -> Tail。
你在 Inbound A 中处理完数据,准备发出去:
ctx.channel().write()(或ctx.pipeline().write()):事件会从 Pipeline 的 Tail (尾部) 开始,一直向前寻找 OutboundHandler。- 执行顺序:
Tail->Outbound C->Outbound B->Head-> 发往网络。
- 执行顺序:
ctx.write():事件会从当前 Handler 的位置开始,向前寻找 OutboundHandler。- 执行顺序:当前在
Inbound A,它的前面只有Head,所以直接跳过Outbound B和C,变成:Inbound A->Head-> 发往网络。(B和C不会被执行!)
- 执行顺序:当前在
因此,合理地编排 Pipeline 中 Handler 的顺序,以及正确使用 Context,是 Netty 编程的关键。
总结
- 一对一:一个
Channel对应一个ChannelPipeline。 - 一对多:一个
ChannelPipeline包含多个ChannelHandler(通过ChannelHandlerContext包装成双向链表)。 - 职责分明:
Pipeline负责维护执行顺序和路由事件,Handler负责具体的业务逻辑处理(如编解码、业务计算)。 - 方向相反:入站事件(Inbound)顺着链表走,出站事件(Outbound)逆着链表走。