前言
接着上篇的ByteBuf,这一篇讲讲ChannelHandler;ChannelHandler在Netty中作为处理Channel中的事件以及数据的一种方式存在,以Channel中还有ChannelPipeline,它负责编排ChannelHandler以使ChannelHandler能有效的协同作业。
Channel
Channel是NIO中的重要组件之一,它持有着远程的数据,处理连接、监听、读、写等事件操作,也就是说连接一个远程服务器、读写网络数据等等都需要Channel这个组件的参与,在Java JDK中使用NIO编程时处理Channel的事件是在Selector中进行手动的注册,每完成一个操作注册一次,然后再通过Selector进行轮询的方式获取有效的的事件对应的Channel进行处理。而在Netty中不再需要这样,Netty中的Channel同样也有事件,但Netty将其进行了包装,将将这些事件修改成了生命周期的概念;
Channel生命同期:
| 状态 | 描述 |
|---|---|
| ChannelUnregistered | Channel已经被创建,但还未注册到EventLoop |
| ChannelRegistered | Channel已经被注册到EventLoop |
| ChannelActive | Channel已经处理活动状态并可以接收与发送数据 |
| ChannelInactive | Channel没有连接到远程节点 |
Channel生命状态图:

ChannelHandler
ChannelHandler是Netty框架中特有的,它是处理Channel中事件一种方式,对于入站与出站消息又分别使用ChannelInboundHandler与ChannelOutboundHandler来处理,但在之前的示例中并没有直接使用这两个类,而是使用了ChannelInboundHandlerAdapter(因为没有处理出站也就没有使用ChannelOutboundHandlerAdapter);下面先来看看ChannelHandler的生命周期方法:
ChannelHandler生命周期:
| 类型 | 描述 |
|---|---|
| handlerAdded | 当把ChannelHandler添加到ChannelPipeline中时被调用 |
| handlerRemoved | 当把ChannelHandler在ChannelPipeline中移除时调用 |
| exceptionCaught | 当ChannelHandler在处理过程中出现异常时调用 |
这个生命周期可以在ChannelHandler的类定义中看到,在使用过程中不会直接使用这个接口,而是会使用子类型ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter。通过命名能看出这两个类是一个适配器,它们的父类是ChannelInboundHandler与ChannelOutboundHandler。
- ChannelOutboundHandler:处理出站数据并且允许拦截所有的操作
- ChannelInboundHandler:处理入站数据以及各种状态的变化
ChannelInboundHandler
在ChannelInboundHandler中可以获取网络数据并处理各种事件,下面列出了能处理的各种事件(这些事件每一个都是ChannelInboundHandler中的方法):
| 事件 | 说明 |
|---|---|
| channelRegistered | 当Channel注册到它的EventLoop并且能够处理I/O时调用 |
| channelUnregistered | 当Channel从它的EventLoop中注销并且无法处理任何I/O时调用 |
| channelActive | 当Channel处理于活动状态时被调用 |
| channelInactive | 当Channel不再是活动状态且不再连接它的远程节点时被调用 |
| channelReadComplete | 当Channel上的一个读操作完成时被调用 |
| channelRead | 当从Channel读取数据时被调用 |
| channelWritabilityChanged | 当Channel的可写状态发生改变时被调用 |
| userEventTriggered | 当ChannelInboundHandler.fireUserEventTriggered()方法被调用时触发 |
在应用中可以通过channelRead方法读取网络数据,但通过直接继承ChannelInboundHandler的子类来说,使用channelRead方法需要注意需要显示的释放与池化ByteBuf实例相关的内存,为此Netty提供了一个实用方法:ReferenceCountUtil.release()方法,在之前的示例中并没有这样使用,所以举一个简单的例子以供参考:
1 |
|
Netty另外还提供了一个类来简化这一过程SimpleChannelInboundHandler类,这个类通过一个通过的过程简化了上面释放内存的操作,使用示例如下:
1 | public class DiscardHandler extends SimpleChannelInboundHandler<Object>{ |
另外Netty还提供了一个资源泄漏的诊断类,它可以帮助我们在调试时发现问题,这个类便是ResourceLeakDetector,它将对应用程序的缓冲区分配做大约1%的采样来检测内存泄漏,当然它不止这一点本事:
| 级别 | 描述 |
|---|---|
| DISABLED | 禁用泄漏检测 |
| SIMPLE | 使用1%的默认采样率检测并报告内存泄漏这是一个默认级别 |
| ADVANCED | 使用默认的采样率报告所发现的任何泄漏以及对应的消息被访问的位置 |
| PARANOID | 类似于ADVANCED,但会对所有的访问进行采样。 |
ChannelOutboundHandler
出站的数据和操作由ChannelOutboundHandler接口处理,它定义的方法将会被Channel、ChannelPipeline、ChannelHandlerContext调用;ChannelOutBoundHandler的强大之处在于可以按需要推迟操作或者事件,这样就可以处理一些相对复杂的请求,例如远程节点暂停写入,那么可以通过ChannelOutboundHandler的处理推迟冲刷操作并在稍后继续。下面来看看ChannelOutboundHandler定义了那些处理方法:
| 方法 | 描述 |
|---|---|
| bind | 当请求将Channel绑定到本地地址时被调用 |
| connet | 当请求将Channel连接到远程节点时被调用 |
| disconnect | 当请求将Channel从远程节点断开时调用 |
| close | 当请求关闭Channel时调用 |
| deregister | 当请求将Channel从它的EventLoop注销时调用 |
| read | 当请求从Channel中读取数据时调用 |
| flush | 当请求通过Channel将入队数据冲刷到远程节点时调用 |
| write | 当请求通过Channel将数据写入远程节点时被调用 |
ChannelHandler关系类图
最后用一张图来看看ChannelHandler、ChannelOutboundHandler、ChannelInboundHandler接口与ChannelHandlerAdapter、ChannelInboundHandlerAdapter、ChannelOutboundHandlerAdapter适配类之前的继承关系:

通过这个类图了解可以得到,当需要处理入站消息时只需要继承ChannelInboundHandlerAdapter就可以实现大部分的功能,包含读、注册等;当需要处理出站消息时只需要继承ChannelOutboundHandlerAdapter同样可以实现。