在日常的开发中,Netty的主要组件是ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的容器,ChannelHandler 的方法是由网络事件触发的。事实上,ChannelHandler 可专门用于几乎任何类型的动作,例如将数据从一种格式转换为另外一种格式,例如各种编解码,或者处理转换过程中所抛出的异常。所以ChannelInboundHandler是一个我们将会经常实现的子接口。
这种类型的ChannelHandler接收入站事件和数据,这些数据随后将会被你的应用程序的业务逻辑所处理。当你要给连接的客户端发送响应时,也可以从ChannelInboundHandler冲刷数据。你的应用程序的业务逻辑通常驻留在一个或者多个ChannelInboundHandler 中。这种类型的ChannelHandler接收入站事件和数据,这些数据随后将会被你的应用程序的业务逻辑所处理。
下面列出了interface ChannelHandler 定义的生命周期操作,在ChannelHandler被添加到ChannelPipeline 中或者被从ChannelPipeline中移除时会调用这些操作。这些方法中的每一个都接受一个ChannelHandlerContext 参数。
handlerAdded
当把ChannelHandler 添加到ChannelPipeline 中时被调用handlerRemoved
当从ChannelPipeline 中移除ChannelHandler 时被调用exceptionCaught
当处理过程中在ChannelPipeline 中有错误产生时被调用Netty 定义了下面两个重要的ChannelHandler 子接口:
下面列出了interface ChannelInboundHandler 的生命周期方法。这些方法将会在数据被接收时或者与其对应的Channel 状态发生改变时被调用。正如我们前面所提到的,这些方法和Channel 的生命周期密切相关。
channelRegistered
当Channel 已经注册到它的EventLoop并且能够处理I/时被调用channelUnregistered
当Channel 从它的EventLoop 注销并且无法处理任何I/O 时被调用channelActive
当Channel 处于活动状态时被调用;Channel 已经连接/绑定并且已经就绪channelInactive
当Channel 离开活动状态并且不再连接它的远程节点时被调用channelReadComplete
当Channel上的一个读操作完成时被调用channelRead
当从Channel 读取数据时被调用ChannelWritability-Changed
当Channel 的可写状态发生改变时被调用。用户可以确保写操作不会完成得太快(以避免发生OutOfMemoryError)或者可以在Channel变为再次可写时恢复写入。userEventTriggered
当ChannelnboundHandler.fireUserEventTriggered()方法被调用时被调用。当某个ChannelInboundHandler 的实现重写channelRead()方法时,它要负责显式地释放与池化的ByteBuf实例相关的内存。Netty 为此提供了一个实用方法ReferenceCountUtil.release()
Netty 将使用WARN 级别的日志消息记录未释放的资源,使得可以非常简单地在代码中发现违规的实例。但是以这种方式管理资源可能很繁琐。一个更加简单的方式是使用SimpleChannelInboundHandler,其会自动释放资源。
出站操作和数据将由ChannelOutboundHandler 处理。它的方法将被Channel、ChannelPipeline 以及ChannelHandlerContext 调用。所有由ChannelOutboundHandler 本身所定义的方法:
bind(ChannelHandlerContext,SocketAddress,ChannelPromise)
当请求将Channel 绑定到本地地址时被调用
connect(ChannelHandlerContext,SocketAddress,SocketAddress,ChannelPromise)
当请求将Channel 连接到远程节点时被调用
disconnect(ChannelHandlerContext,ChannelPromise)
当请求将Channel 从远程节点断开时被调用
close(ChannelHandlerContext,ChannelPromise)
当请求关闭Channel 时被调用
deregister(ChannelHandlerContext,ChannelPromise)
当请求将Channel 从它的EventLoop 注销时被调用
read(ChannelHandlerContext)
当请求从Channel 读取更多的数据时被调用
flush(ChannelHandlerContext)
当请求通过Channel 将入队数据冲刷到远程节点时被调用
write(ChannelHandlerContext,Object,ChannelPromise)
当请求通过Channel 将数据写到远程节点时被调用
有一些适配器类可以将编写自定义的ChannelHandler所需要的努力降到最低限度,因为它们提供了定义在对应接口中的所有方法的默认实现。因为你有时会忽略那些不感兴趣的事件,所以Netty提供了抽象基类ChannelInboundHandlerAdapter 和ChannelOutboundHandlerAdapter。
你可以使用ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter类作为自己的ChannelHandler 的起始点。这两个适配器分别提供了ChannelInboundHandler和ChannelOutboundHandler的基本实现。通过扩展抽象类ChannelHandlerAdapter,它们获得了它们共同的超接口ChannelHandler 的方法。
ChannelHandlerAdapter还提供了实用方法isSharable()。如果其对应的实现被标注为Sharable,那么这个方法将返回true,表示它可以被添加到多个ChannelPipeline。
当Channel被创建时,它会被自动地分配到它专属的ChannelPipeline。每一个新创建的Channel都将会被分配一个新的ChannelPipeline。这项关联是永久性的,Channel既不能附加另外一个ChannelPipeline,也不能分离其当前的。在Netty 组件的生命周期中,这是一项固定的操作,不需要开发人员的任何干预。
这样使得事件流经ChannelPipeline是ChannelHandler的工作,它们是在应用程序的初始化或者引导阶段被安装的。这些对象接收事件、执行它们所实现的处理逻辑,并将数据传递给链中的下一个ChannelHandler。它们的执行顺序是由它们被添加的顺序所决定的。
入站和出站ChannelHandler可以被安装到同一个ChannelPipeline中。如果一个消息或者任何其他的入站事件被读取,那么它会从ChannelPipeline的头部开始流动,最终数据将会到达ChannelPipeline 的尾端,这样所有处理就都结束了。
数据的出站运动(即正在被写的数据)在概念上也是一样的。在这种情况下,数据将从ChannelOutboundHandler链的尾端开始流动,直到它到达链的头部为止。在这之后,出站数据将会到达网络传输层,这里显示为Socket。通常情况下,这将触发一个写操作。
如果将两个类别的ChannelHandler都混合添加到同一个ChannelPipeline中会发生什么。虽然ChannelInboundHandle和ChannelOutboundHandle都扩展自ChannelHandler,但是Netty 能区分ChannelInboundHandler实现和ChannelOutboundHandler实现,并确保数据只会在具有相同定向类型的两个ChannelHandler之间传递。
addFirst()、addBefore()、addAfter()、addLast()
将一个ChannelHandler 添加到ChannelPipeline中
remove()
将一个ChannelHandler 从ChannelPipeline中移除
replace()
将ChannelPipeline 中的一个ChannelHandler替换为另一个ChannelHandler
get()
通过类型或者名称返回ChannelHandler
context()
返回和ChannelHandler绑定的ChannelHandlerContext
names()
返回ChannelPipeline中所有ChannelHandler 的名称ChannelPipeline的API 公开了用于调用入站和出站操作的附加方法。
通过使用作为参数传递到每个方法的ChannelHandlerContext,事件可以被传递给当前ChannelHandler 链中的下一个ChannelHandler。虽然这个对象可以被用于获取底层的Channel,但是它主要还是被用于写出站数据。
ChannelHandlerContext代表了ChannelHandler 和ChannelPipeline之间的关联,每当有ChannelHandler 添加到ChannelPipeline 中时,都会创建ChannelHandlerContext。ChannelHandlerContext 的主要功能是管理它所关联的ChannelHandler 和在同一个ChannelPipeline 中的其他ChannelHandler 之间的交互。
ChannelHandlerContext 有很多的方法,其中一些方法也存在于Channel 和ChannelPipeline 本身上,但是有一点重要的不同。
如果调用Channel或者ChannelPipeline上的这些方法,它们将沿着整个ChannelPipeline 进行传播。而调用位于ChannelHandlerContext上的相同方法,则将从当前所关联的ChannelHandler开始,并且只会传播给位于该ChannelPipeline 中的下一个(入站下一个,出站上一个)能够处理该事件的ChannelHandler。
至于其(入站下一个,出站上一个)是有一定的编码的如下:
如果调用Channel或者ChannelPipeline上的这些方法,可能会从5开始进行传播(如果后来的还有出站处理器6、7、8…,则会从最后一个8…开始向前传播),而直接调用ChannelHandlerContext的方法,则会从3开始,这样传播路径就会较短。
当使用ChannelHandlerContext 的API 的时候,有以下两点:
alloc()
返回和这个实例相关联的Channel 所配置的ByteBufAllocator
bind()
绑定到给定的SocketAddress,并返回ChannelFuture
channel()
返回绑定到这个实例的Channel
close()
关闭Channel,并返回ChannelFuture
connect()
连接给定的SocketAddress,并返回ChannelFuture
deregister()
从之前分配的EventExecutor 注销,并返回ChannelFuture
disconnect()
从远程节点断开,并返回ChannelFuture
executor()
返回调度事件的EventExecutor
fireChannelActive()
触发对下一个ChannelInboundHandler上的channelActive()方法(已连接)的调用
fireChannelInactive()
触发对下一个ChannelInboundHandler上的channelInactive()方法(已关闭)的调用
fireChannelRead()
触发对下一个ChannelInboundHandler上的channelRead()方法(已接收的消息)的调用
fireChannelReadComplete()
触发对下一个ChannelInboundHandler上的channelReadComplete()方法的调用
fireChannelRegistered()
触发对下一个ChannelInboundHandler上的fireChannelRegistered()方法的调用
fireChannelUnregistered()
触发对下一个ChannelInboundHandler上的fireChannelUnregistered()方法的调用
fireChannelWritabilityChanged()
触发对下一个ChannelInboundHandler上的fireChannelWritabilityChanged()方法的调用
fireExceptionCaught()
触发对下一个ChannelInboundHandler上的fireExceptionCaught(Throwable)方法的调用
fireUserEventTriggered()
触发对下一个ChannelInboundHandler上的fireUserEventTriggered(Object evt)方法的调用
handler()
返回绑定到这个实例的ChannelHandler
isRemoved()
如果所关联的ChannelHandler已经被从ChannelPipeline中移除则返回true
name()
返回这个实例的唯一名称
pipeline()
返回这个实例所关联的ChannelPipeline
read()
将数据从Channel读取到第一个入站缓冲区;如果读取成功则触发一个channelRead事件,并(在最后一个消息被读取完成后)通知ChannelInboundHandler的channelReadComplete(ChannelHandlerContext)方法