Interface Channel 定义了一组和 ChannelInboundHandler API 密切相关的简单但 功能强大的状态模型,表 6-1 列出了 Channel 的这 4 个状态。
Channel 的正常生命周期如图 6-1 所示。当这些状态发生改变时,将会生成对应的事件。 这些事件将会被转发给 ChannelPipeline 中的 ChannelHandler,其可以随后对它们做出 响应。
表 6-2 中列出了 interface ChannelHandler 定义的生命周期操作,在 ChannelHandler 被添加到 ChannelPipeline 中或者被从 ChannelPipeline 中移除时会调用这些操作。这些 方法中的每一个都接受一个 ChannelHandlerContext 参数。
Netty 定义了下面两个重要的 ChannelHandler 子接口:
ChannelInboundHandler——处理入站数据以及各种状态变化;
表 6-3 列出了 interface ChannelInboundHandler 的生命周期方法。这些方法将会在 数据被接收时或者与其对应的 Channel 状态发生改变时被调用。正如我们前面所提到的,这些 方法和 Channel 的生命周期密切相关。
出站操作和数据将由 ChannelOutboundHandler 处理。它的方法将被 Channel、Channel- Pipeline 以及 ChannelHandlerContext 调用。
ChannelOutboundHandler 的一个强大的功能是可以按需推迟操作或者事件,这使得可 以通过一些复杂的方法来处理请求。例如,如果到远程节点的写入被暂停了,那么你可以推迟冲 刷操作并在稍后继续。
表 6-4 显示了所有由 ChannelOutboundHandler 本身所定义的方法(忽略了那些从 Channel- Handler 继承的方法)。
ChannelPromise与ChannelFuture ChannelOutboundHandler中的大部分方法都需要一个 ChannelPromise参数,以便在操作完成时得到通知。ChannelPromise是ChannelFuture的一个 子类,其定义了一些可写的方法,如setSuccess()和setFailure(),从而使ChannelFuture不可变。
你可以使用 ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 类作为自己的 ChannelHandler 的起始点。这两个适配器分别提供了 ChannelInboundHandler 和 ChannelOutboundHandler 的基本实现。通过扩展抽象类 ChannelHandlerAdapter,它们 获得了它们共同的超接口 ChannelHandler 的方法。生成的类的层次结构如图 6-2 所示。
每一个新创建的 Channel 都将会被分配一个新的 ChannelPipeline。这项关联是永久性 的;Channel 既不能附加另外一个 ChannelPipeline,也不能分离其当前的。在 Netty 组件 的生命周期中,这是一项固定的操作,不需要开发人员的任何干预。
根据事件的起源,事件将会被 ChannelInboundHandler 或ChannelOutboundHandler处理。随后,通过调用 ChannelHandlerContext 实现,它将被转发给同一超类型的下一个ChannelHandler。
ChannelHandlerContext使得ChannelHandler能够和它的ChannelPipeline以及其他的 ChannelHandler 交 互 。 ChannelHandler 可 以 通 知 其 所 属 的 ChannelPipeline 中 的 下 一 个 ChannelHandler,甚至可以动态修改它所属的ChannelPipeline1。
ChannelHandlerContext 具有丰富的用于处理事件和执行 I/O 操作的 API。
图 6-3 展示了一个典型的同时具有入站和出站 ChannelHandler 的 ChannelPipeline 的布 局,并且印证了我们之前的关于 ChannelPipeline 主要由一系列的 ChannelHandler 所组成的 说 法 。C h a n n e l P i p e l i n e 还 提 供 了 通 过 C h a n n e l P i p e l i n e 本 身 传 播 事 件 的 方 法 。如 果 一 个 入 站 事件被触发,它将被从 ChannelPipeline 的头部开始一直被传播到 Channel Pipeline 的尾端。 在图 6-3 中,一个出站 I/O 事件将从 ChannelPipeline 的最右边开始,然后向左传播。
ChannelHandler 可以通过添加、删除或者替换其他的 ChannelHandler 来实时地修改 ChannelPipeline 的布局。(它也可以将它自己从 ChannelPipeline 中移除。)这是 Channel- Handler 最重要的能力之一,所以我们将仔细地来看看它是如何做到的。表 6-6 列出了相关的方法。
ChannelPipeline 的 API 公开了用于调用入站和出站操作的附加方法。
总结一下:
ChannelPipeline 保存了与 Channel 相关联的 ChannelHandler;
ChannelPipeline 可以根据需要,通过添加或者删除 ChannelHandler 来动态地修改;
ChannelHandlerContext 代表了 ChannelHandler 和 ChannelPipeline 之间的关 联,每当有 ChannelHandler 添加到 ChannelPipeline 中时,都会创建 ChannelHandler- Context。
ChannelHandlerContext 的主要功能是管理它所关联的 ChannelHandler 和在 同一个 ChannelPipeline 中的其他 ChannelHandler 之间的交互。
ChannelHandlerContext 有很多的方法,其中一些方法也存在于 Channel 和 Channel- Pipeline 本身上,但是有一点重要的不同。如果调用 Channel 或者 ChannelPipeline 上的这 些方法,它们将沿着整个 ChannelPipeline 进行传播。而调用位于 ChannelHandlerContext 上的相同方法,则将从当前所关联的 ChannelHandler 开始,并且只会传播给位于该 ChannelPipeline 中的下一个能够处理该事件的 ChannelHandler。
当使用 ChannelHandlerContext 的 API 的时候,请牢记以下两点:
ChannelHandlerContext 和 ChannelHandler 之间的关联(绑定)是永远不会改变的,所以缓存对它的引用是安全的;
如同我们在本节开头所解释的一样,相对于其他类的同名方法,ChannelHandlerContext的方法将产生更短的事件流,应该尽可能地利用这个特性来获得最大的性能。
要想调用从某个特定的 ChannelHandler 开始的处理过程,必须获取到在Channel
Pipeline上该 ChannelHandler 之前的 ChannelHandler 所关联的 ChannelHandlerContext。这个 ChannelHandlerContext 将调用和它所关联的 ChannelHandler 之后的 ChannelHandler。
因为一个 ChannelHandler 可以从属于多个 ChannelPipeline,所以它也可以绑定到多 个 ChannelHandlerContext 实例。对于这种用法指在多个 ChannelPipeline 中共享同一 个 ChannelHandler,对应的 ChannelHandler 必须要使用@Sharable 注解标注;否则, 试图将它添加到多个 ChannelPipeline 时将会触发异常。显而易见,为了安全地被用于多个 并发的 Channel(即连接),这样的 ChannelHandler 必须是线程安全的。
为何要共享同一个ChannelHandler?
在多个ChannelPipeline中安装同一个ChannelHandler的一个常见的原因是用于收集跨越多个 Channel 的统计信息。