前言

接着上篇的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生命状态图:

image-20210719103204408

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
2
3
4
5
6
7
8
9
@Sharable
//Discradhandler扩展自ChannelInboundHandlerAdapter
public class DiscardHandler extends ChannelInboundHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext ctx,Object msg){
//释放msg
ReferenceCountUtil.release(msg);
}
}

Netty另外还提供了一个类来简化这一过程SimpleChannelInboundHandler类,这个类通过一个通过的过程简化了上面释放内存的操作,使用示例如下:

1
2
3
4
5
6
public class DiscardHandler extends SimpleChannelInboundHandler<Object>{
@Override
public void channelRead0(ChannelHandlerContext ctx,Object msg){
//在这里不需要显示的对资源msg进行释放
}
}

另外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适配类之前的继承关系:

image-20210719113249134

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