netty源码之ChannelHandlerContext

  • 一、ChannelHandlerContext是什么?
  • 二、ChannelHandlerContext的API
  • 1、channel()、pipeline()、handler()、alloc()、executor()
  • 2、fire类方法
  • 3、read()
  • 4、write()
  • 三、骨架类AbstractChannelHandlerContext
  • 1、alloc()方法
  • 2、read()方法
  • 3、write方法
  • 四、实现类HeadContext
  • 1、read()方法
  • 2、write()方法
  • 3、channelActive()方法
  • 五、实现类TailContext
  • 1、channelRead()方法
  • 六、实现类DefaultChannelHandlerContext

一、ChannelHandlerContext是什么?

ChannelHandlerContext代表了ChannelHandler和ChannelPipline之间的关联,每当有一个ChannelHandler添加到ChannelPipline中时,都会创建一个ChannelHandlerContext。主要功能是管理它所关联的ChannelHandler和在同一个ChannelPipline中的其他ChannelHandler之间的交互。

netty channel写入redis_网络

二、ChannelHandlerContext的API

netty channel写入redis_网络协议_02

1、channel()、pipeline()、handler()、alloc()、executor()

返回该ChannelHandlerContext绑定的各种组件。

2、fire类方法

触发对pipeline上下一个ChannelInBoundHandler上的对应方法的调用。

3、read()

将数据从Channel读取到第一个入站缓冲区;如果读取成功则触发一个channelRead事件,并在最后一个消息被读取完成后通知ChannelInBoundHandler的channelReadComplete(ctx)方法。

4、write()

通过该ctx写入消息。

三、骨架类AbstractChannelHandlerContext

在AbstractChannelHandlerContext类中实现了ChannelHandlerContext的绝大多数方法逻辑。

1、alloc()方法

netty channel写入redis_java_03


通过pipeline获取channel,再获取到对应的ChannelConfig,调用DefaultChannelConfig的getAllocator()方法。

netty channel写入redis_网络协议_04

private volatile ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR;

netty channel写入redis_类方法_05


可以看到,只有android是默认使用unpooled的,其他均使用pooled。

alloc = PooledByteBufAllocator.DEFAULT;
public static final PooledByteBufAllocator DEFAULT =
            new PooledByteBufAllocator(PlatformDependent.directBufferPreferred());

至此,我们看到默认的分配器是PooledByteBufAllocator。

2、read()方法

netty channel写入redis_网络协议_06

首先会寻找下一个出站处理器,然后取其线程调用其read方法。其实普通的ctx在read的时候就是做了一个传递,真正有执行逻辑的就是pipeline的最后一个出站处理器,也就是HeadContext。接第四节第1个方法描述。

3、write方法

netty channel写入redis_java_07

注意到,在进行基础检验或者发生异常时,调用了ReferenceCountUtil.release(msg)方法进行内存释放。

netty channel写入redis_网络协议_08

接下来就如出一辙了,沿着出站处理器往下传递,直到最后一个HeadContext。

4、file类方法fireChannelRead()方法

netty channel写入redis_java_09

找到下一个进站处理器,触发对应方法的调用。

四、实现类HeadContext

pipeline中的第一个ctx,既是入站处理器,也是出站处理器。

1、read()方法

netty channel写入redis_数据_10


调用了unsafe的beginRead方法。

netty channel写入redis_类方法_11


这个unsafe是channel的unsafe。

netty channel写入redis_数据_12


该方法在AbstractChannel中实现。调用了钩子方法doBeginRead。我们进AbstractNioChannel类继续追踪

netty channel写入redis_类方法_13

最终是给该channel在selector上注册了读事件。

2、write()方法

netty channel写入redis_网络_14

同样也是调用了unsafe的write方法。

netty channel写入redis_数据_15

可以看到是把数据写到了出站缓冲区中。

3、channelActive()方法

netty channel写入redis_数据_16

除了向后传递,还调用了readIfIsAutoRead()方法。

netty channel写入redis_网络_17


这个方法也很简单,调用了channel的read方法,最终调用了HeadContext的read方法,也就是本节介绍的第1个方法,为该channel注册了读事件。

五、实现类TailContext

pipeline中的最后一个ctx,入站处理器链的最后一个节点。

1、channelRead()方法

netty channel写入redis_网络_18

看到仅仅是调用了DefaultChannelPipline的onUnhandledInboundMessage()方法。

netty channel写入redis_网络协议_19

在这个方法中,只是打印了日志,并对内存进行了释放。这就告诉我们,在我们自定义实现的inBoundHandler中,只要我们调用file方法把msg传递下去,netty已经给我们做好了内存回收,不用担心内存泄露问题。

六、实现类DefaultChannelHandlerContext

netty channel写入redis_类方法_20

可以看到在该类中并没有过多的方法实现,这也就是netty的设计思想,尽量在Abstract类中把该实现的都实现了。