1.什么是ChannelHandler和ChanneIPipeline
ChannelHandler是一个包含所有应用处理逻辑的容器载体,用来对Netty的输入输出数据进行加工处理。
比如数据格式转换、异常处理等
ChannelPipeline 则是 ChannelHandler 的容器载体,负责以链式的形式调度各个注册的ChannelHandler。
我们回顾下之前介绍过的Netty逻辑架构,观察下ChannelPipeline和ChannelHandler的位置。
再从局部放大,可以更加明确地看到ChannelPipeline和ChannelHandler的作用。
如上图所示,当EventLoop中监听到事件后,会对I/O事件进行处理。而这个处理,就是交给ChannelPipeline进行,更严格地说,是交给ChannelPipeline中的各个ChannelHandler按照一定的顺序进行处理。
根据数据的流向,Netty把ChannelHandler分为2类,InboundHandler和OutboundHandler。
如上图所示,Netty接收到数据后,经过若干 InboundHandler 处理后接收成功。如果要输出数据,就需要经过若干个 OutboundHandler 处理完成后发送。
比如,我们经常需要对接收到的数据进行解码,就是在某一个专门decode的InboundHandler中处理的。如果要发送数据,往往需要编码,就是在某一个专门encode的OutBoundHandler中处理的。
值得一提的是,虽然我们在使用Netty时,直接打交道的是ChannelPipeline和ChannelHandler,但是,它们之间有一座“隐形”的桥梁,名字叫做ChannelHandlerContext。
顾名思义,ChannelHanderContext就是ChannelHandler的上下文,每个 ChannelHandler 都对应一个 ChannelHandlerContext。
每一个 ChannelPipeline 都包含多个 ChannelHandlerContext,所有 ChannelHandlerContext 之间组成了双向链表。如下图所示。
其中,有两个特殊的ChannelHandlerContext,分别是HeadContext和TailContext,表示双向链表的头尾节点。
从类图上可以看到,HeadContext同时实现了ChannelInboundHandler和ChannelOutboundHandler。因此,HeadContext在读取数据时作为头节点,向后传递InBound事件,同时,在写数据时作为尾节点,处理最后的OutBound事件。
TailContext只实现了ChannelInboundHandler。它在InBound事件传递的末尾,负责处理一些资源释放的工作。在OutBound事件传递的第一个节点,不做任何处理,仅仅传递OutBound事件给prev节点。
而我们平时自定义的ChannelHandler,就是插在这两个头尾节点之间的。
至此,我们对ChannelHandler和ChannelPipeline有了基本的认识。具体到实践上,我们该如何正确地使用ChannelHandler呢?
对ChannelHandler的使用,必须先了解ChannelHandler的事件传播机制和异常处理机制。
2.ChanneIHandler的事件传播机制
前面我们提到了Netty中的两种事件类型,Inbound事件和Outbound事件,分别对应InboundHandler和OutbountHandler进行处理。
当我们使用Netty进行开发的时候,必须了解Inbound事件和Outbound事件在ChannelPipeline中如何进行“事件传播”,注册InboundHandler和OutboundHandler的顺序有什么影响。
话不多说,我们先来一个demo直观地感受一下。
自定义一个ChannelInboundHandler
自定义一个ChannelOutboundHandler
简单组装一下EchoPipelineServer,特别注意一下 6个handler 的注册顺序。
然后我们通过命令行简单访问一下这个Netty Server
curl localhost:8081
可以看到控制台的如下输出
这样就清楚了事件传播顺序:
- 对于Inbound事件,InboundHandler的处理顺序是和注册顺序一致
- 对于Outbound事件,OutboundHandler的处理顺序和注册顺序相反
结合上一节说的HeadContext和TailContext,我们画个图来更直观地看一下这个ChannelPipeline中的handler构建顺序是怎样的。
在上面的ChannelInitializer中,我们按需添加了3个InboundHandler和3个OutboundHandler。所以,在头节点HeadContext和TailContext之间,有序构成了双向链表。
而InboundHandler3中,通过调用 ctx.channel.writeAndFlush( msg ) 方法,将消息从TailContext开始,依据OutboundHandler的路径向HeadContext方向传播出去。具体可以看下DefaultChannelPipeline类中的实现
虽然这里是双向链表,但是无论是Inbound事件还是Outbound事件,在按序访问链表节点时,会根据事件类型进行过滤。