
一文聊透Netty IO事件的编排利器pipeline |详解所有IO事件(二)
2.1 HeadContext
我们知道双向链表结构的 pipeline 中的节点元素为 ChannelHandlerContext ,既然 HeadContext 作为 pipeline 的头结点,那么它一定是 ChannelHandlerContext 类型的,所以它需要继承实现 AbstractChannelHandlerContext ,相当于一个哨兵的作用,因为用户可以以任意顺序向 pipeline 中添加 ChannelHandler ,需要用 HeadContext 来固定指向第一个 ChannelHandlerContext 。
在《一文搞懂Netty发送数据全流程》 一文中的《1. ChannelHandlerContext》小节中,笔者曾为大家详细介绍过 ChannelHandlerContext 在 pipeline 中的作用,忘记的同学可以在回看下。
于此同时 HeadContext 又实现了 ChannelInboundHandler 和 ChannelOutboundHandler 接口,说明 HeadContext 即是一个 ChannelHandlerContext 又是一个 ChannelHandler ,它可以同时处理 Inbound 事件和 Outbound 事件。
我们也注意到 HeadContext 中持有了对应 channel 的底层操作类 unsafe ,这也说明 IO 事件在 pipeline 中的传播最终会落在 HeadContext 中进行最后的 IO 处理。它是 Inbound 事件的处理起点,也是 Outbound 事件的处理终点。这里也可以看出 HeadContext 除了起到哨兵的作用,它还承担了对 channel 底层相关的操作。
比如我们在《Reactor在Netty中的实现(启动篇)》中介绍的 NioServerSocketChannel 在向 main reactor 注册完成后会触发 ChannelRegistered 事件从 HeadContext 开始依次在 pipeline 中向后传播。
以及 NioServerSocketChannel 在与端口绑定成功后会触发 ChannelActive 事件从 HeadContext 开始依次在 pipeline 中向后传播,并在 HeadContext 中通过 unsafe.beginRead() 注册 OP_ACCEPT 事件到 main reactor 中。
同理在 NioSocketChannel 在向 sub reactor 注册成功后。会先后触发 ChannelRegistered 事件和 ChannelActive 事件从 HeadContext 开始在 pipeline 中向后传播。并在 HeadContext 中通过 unsafe.beginRead() 注册 OP_READ 事件到 sub reactor 中。
在《一文搞懂Netty发送数据全流程》中介绍的 write 事件和 flush 事件最终会在 pipeline 中从后向前一直传播到 HeadContext ,并在 HeadContext 中相应事件回调函数中调用 unsafe 类操作底层 channel 发送数据。
从本小节的内容介绍中,我们可以看出在 Netty 中对于 Channel 的相关底层操作调用均是在 HeadContext 中触发的。
2.2 TailContext
同样 TailContext 作为双向链表结构的 pipeline 中的尾结点,也需要继承实现 AbstractChannelHandlerContext 。但它同时又实现了 ChannelInboundHandler 。
这说明 TailContext 除了是一个 ChannelHandlerContext 同时也是一个 ChannelInboundHandler 。
2.2.1 TailContext 作为一个 ChannelHandlerContext 的作用
TailContext 作为一个 ChannelHandlerContext 的作用是负责将 outbound 事件从 pipeline 的末尾一直向前传播直到 HeadContext 。当然前提是用户需要调用 channel 的相关 outbound 方法。
这里我们可以看到,当我们在自定义 ChannelHandler 中调用 ctx.channel().write(msg) 时,会在 AbstractChannel 中触发 pipeline.write(msg) ,最终在 DefaultChannelPipeline 中调用 tail.write(msg) 。使得 write 事件可以从 pipeline 的末尾开始向前传播,其他 outbound 事件的传播也是一样的道理。
而我们自定义的 ChannelHandler 会被封装在一个 ChannelHandlerContext 中从而加入到 pipeline 中,而这个用于装载自定义 ChannelHandler 的 ChannelHandlerContext 与 TailContext 一样本质也都是 ChannelHandlerContext ,只不过在 pipeline 中的位置不同罢了。
客户端channel pipeline结构.png
我们看到 ChannelHandlerContext 接口本身也会继承 ChannelInboundInvoker 和 ChannelOutboundInvoker 接口,所以说 ContextHandlerContext 也可以触发 inbound 事件和 outbound 事件,只不过表达的语义是在 pipeline 中从当前 ChannelHandler 开始向前或者向后传播 outbound 事件或者 inbound 事件。
这里表示 write 事件从当前 EchoServerHandler 开始在 pipeline 中向前传播直到 HeadContext 。
客户端channel pipeline结构.png
2.2.2 TailContext 作为一个 ChannelInboundHandler 的作用
最后 TailContext 作为一个 ChannelInboundHandler 的作用就是为 inbound 事件在 pipeline 中的传播做一个兜底的处理。
这里提到的兜底处理是什么意思呢?
比如我们前边介绍到的,在 NioSocketChannel 向 sub reactor 注册成功后之后触发的 ChannelRegistered 事件和 ChannelActive 事件。或者在 reactor 线程读取 NioSocketChannel 中的请求数据时所触发的 channelRead 事件和 ChannelReadComplete 事件。
这些 inbound 事件都会首先从 HeadContext 开始在 pipeline 中一个一个的向后传递。
极端的情况是如果 pipeline 中所有 ChannelInboundHandler 中相应的 inbound 事件回调方法均不对事件作出处理,并继续向后传播。如下示例代码所示:
最终这些 inbound 事件在 pipeline 中得不到处理,最后会传播到 TailContext 中。
而在 TailContext 中需要对这些得不到任何处理的 inbound 事件做出最终处理。比如丢弃该 msg,并释放所占用的 directByteBuffer,以免发生内存泄露。
