ChannelHandler的适配器
有一些适配器类可以将编写自定义的ChannelHandler所需要的工作降到最低限度,
因为它们提供了定义在对应接口中的所有方法的默认实现。因为有时会忽略那些不感兴趣的
事件,所以Netty提供了抽象积累ChannelInboundHandlerAdapter(处理入站)和
ChannelOutboundHandlerAdapter(处理出站)的基本实现,通过扩展抽象类ChannelHandlerAdapter,
它们获得了它们共同的超接口ChannelHandler的方法.不过ChannelOutboundHandler有个非常让人
迷惑的read()方法,ChannelOutboundHandler不是处理出站事件的吗?怎么会有read()方法呢?
其实这个read方法不是表示读数据,而是表示业务发出了读(read)数据的要求,这个要求也会封装为
一个事件进行传播,这个事件因为时业务发出到网络的,自然就是个出站事件,而且这个事件触发的就是ChannelOutboundHandler中read()方法。如果Handler纪要处理入站又要处理出站怎么办呢?这个时候就可以使用类ChannelDuplexHandler,当然也可以同时实现ChannelOutboundHandler,ChannelInboundHandler这两个接口
Handler的共享和并发安全性
ChannelHandlerAdapter还提供了实用方法isSharable().如果其对应的实现被标注为Sharable,那么这个方法,将返回true,表示它可以被添加到多个ChannelPipeline.这就牵涉到了我们实现的Handler的共享性和线程安全性。在往pipeline安装Handler的时候,基本上都是new出Handler的实例,因为每个socketChannel有自己的pipeline,而且每个socketChannel又是和线程绑定的,所以这些Handler的实例之间完全独立的,只要Handler的实例不是共享了全局变量,Handler的实例是线程安全的
但是如果业务需要在多个SocketChannel之间共享一个Handler的实例怎么办呢?比如统计服务器接收到和发出的业务报文总数,我们就需要用一个Handler的实例来横跨所有的socketChannel来统计socketChannel业务报文数。为了实现这一点,我们可以实现一个MessageCountHandler,并且在MessageCountHandler上使用Netty的@Sharable,然后在安装MessageCountHandler到MessageCountHandler的统计功能时,请务必注意线程安全,具体实现时就使用Java并发编程里的Atomic类来保证这一点
资源管理和SimpleChannelInboundHandler
回想一下我们在NIO中时如何接收和发送网络数据的?都是首先创建了一个Buffer,应用程序中的业务部分和Channel之间通过Buffer进行数据的交换:ByteBuffer.allocate(1024),如图所示
Netty在处理网路数据时,同样也需要Buffer,在Read网络数据时由Netty创建Buffer,Write网络数据时Buffer往往由业务方创建的。不管是读和写,Buffer用完后都必须进行释放,否则可能会造成内存泄漏,在Write网络数据时,可以确保数据被写往网络了,Netty会自动进行Buffer的释放,但是如果Write网络数据时,我们有outBoundHandler处理了write()操作并丢弃了数据,没有继续往下写,要由我们负责释放这个Buffer,就必须调用ReferenceCountUtil.release()方法,否则就可能造成内存泄漏。在Read网络数据时,如果我们可以确保每个Inboundhandler都把数据往后传递了,也就是调用了相关的
fireChannelRead()方法,Netty也会帮我们释放,同样的,如果我们有InboundHandler处理了数据,又不继续往后传递,又不调用负责释放的ReferenceCountUtil.release()方法,就可能会造成内存泄漏。
但是由于消费入站数据是一项常规任务,所以Netty提供了一个特殊的被称为SimpleChannelInboundHandler的ChannelInboundHandler实现,这个实现会在数据被channelRead0()方法消费之后自动释放数据,同时系统为我们提供的各种预定义的Handler实现,都实现了数据的正确处理,所以我们自行在编写业务Handler时,也需要注意:要么继续传递,
要那么自行释放