Netty4事件处理传播机制

private void addFirst0(AbstractChannelHandlerContext newCtx) {

AbstractChannelHandlerContext nextCtx = head.next;

newCtx.prev = head;

newCtx.next = nextCtx;

head.next = newCtx;

nextCtx.prev = newCtx;

}

这里就是典型的链表操作过程。

如果使用如下代码构建事件链,那事件是如何传播的呢?

p.addLast(“1”, new InboundHandlerA());

p.addLast(“2”, new InboundHandlerB());

p.addLast(“3”, new OutboundHandlerA());

p.addLast(“4”, new OutboundHandlerB());

p.addLast(“5”, new InboundOutboundHandlerX());

其构建的事件链最终如图所示:

在这里插入图片描述

但ChannelInboundHandler中的事件是如何传播的呢?ChannelOutboundHandler的事件又是如何传播的呢?

事件链中的节点对象为AbstractChannelHandlerContext,其类图如下:

在这里插入图片描述

  • HeadContext:事件链的头节点。

  • TailContext:事件链的尾节点。

  • DefaultChannelHandlerContext:用户定义的Handler所在的节点。

[](()2.2 事件传播

inbound事件与outbound事件传播机制实现原理相同,只是方向不同,inbound事件的传播从HeadContext开始,沿着next指针进行传播,而outbound事件传播从TailContext开始,沿着prev指针向前传播,故下文重点分析inbound事件传播机制。

DefaultChannelPipeline有关于ChannelInboundHandler的方法实现如下:

在这里插入图片描述

所有的入端事件的传播入口都是从head开始传播。接下来我们以channelRead事件的传播为例,展示inbound的事件的流转。注意:以下观点都是针对NIO的读取。

DefaultChannelPipeline#fireChannelRead(Object msg) {

public final ChannelPipeline fireChannelRead(Object msg) {

AbstractChannelHandlerContext.invokeChannelRead(head, msg); // @1

return this;

}

首先在NIO事件选择器在网络读事件就绪后,会调用底层SocketChanel#read 方法从读缓存中读取字节,在Netty中使用ByteBuf来存储,然后调用DefaultChannelPipeline # fireChannelRead 方法进行事件传播,每个ChannelHandler针对输入进行加工处理,ChannelPipeline因此而得名,有关Netty基于NIO的事件就绪选择实现将在Netty线程模型、IO读写流程部分详细讲解。

从代码@1处可得知,通过AbstractChannelHandlerContext的静态方法invokerChanelRead,从HeadContext处开始执行,

AbstractChannelHandlerContext#invokerChanelRead

static void invokeChannelRead(final AbstractChannelHandlerContext next, final Object msg) {

ObjectUtil.checkNotNull(msg, “msg”);

EventExecutor executor = next.executor(); // @1

if (executor.inEventLoop()) { // @2

next.invokeChannelRead(msg);

} else {

executor.execute(new Runnable() { // @3

@Override

public void run() {

next.invokeChannelRead(msg);

}

});

}

}

这种写法是Netty处理事件执行的“模板”方法,都是先获取需要执行的线程组(EventLoop),如果当前线程不属于Eventloop,则将任务提交到EventLoop中异步执行,如果在,则直接调用。第一次调用,该next指针为HeadContext,那接下来重点关注一下HeadContext的invokeChannelRead方法。

AbstractChannelHandlerContext#invokeChannelRead

private void invokeChannelRead(Object msg) {

if (invokeHandler()) { // @1

try {

((ChannelInboundHandler) handler()).channelRead(this, msg); // @2

} catch (Throwable t) {

notifyHandlerException(t);

}

} else {

fireChannelRead(msg); // @3

}

}

代码@1:如果该通道已经成功添加@1,则执行对应的事件@2,否则只是传播事件@3。

传播事件在AbstractChannelHandlerContext的实现思路如下:

AbstractChannelHandlerContext#fireChannelRead

public ChannelHandlerContext fireChannelRead(final Object msg) {

invokeChannelRead(findContextInbound(), msg);

return this;

}

private AbstractChannelHandlerContext findContextInbound() {

AbstractChannelHandlerContext ctx = this;

do {

ctx = ctx.next;

} while (!ctx.inbound);

return ctx;

}

上述就从事件链中按顺序提取inbound类型的处理器,上述代码要最终能结束,那么TailContext必须是Inbound类型的事件处理器。

从代码@2中执行完对应的事件处理逻辑后,事件如何向下传播呢?如果需要继续将事件传播的话,请调用ChannelInboundHandlerAdapter 对应的传播事件方法,如上例中的 ChannelInboundHandlerAdapter#fireChannelRead,该方法会将事件链继续往下传播,如果在对应的事件处理中继续调用fireChannelRead,则事件传播则停止传播,也就是并不是事件一定会顺着整个调用链到达事件链的尾部TailContext,在实践中请特别重视。

Netty inbound 事件传播流程图如下:

在这里插入图片描述

上述主要分析了inboud事件的传播机制,为了加深理解,我们接下来浏览一下HeadContext、TailContext是如何实现各个事件方法的,这些事件,后续在梳理Netty读写流程时会再详细介绍。

[](()2.3 源码分析DefaultChannelPipeline$HeadContex

[](()2.3.1 HeadContext声明与构造方法

final class HeadContext extends AbstractChannelHandlerContext

implements ChannelOutboundHandler, ChannelInboundHandler { // @1

private final Unsafe unsafe; // @2

HeadContext(Def 《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 aultChannelPipeline pipeline) {

super(pipeline, null, HEAD_NAME, false, true);

unsafe = pipeline.channel().unsafe();

setAddComplete();

}

}

代码@1:HeadContext实现ChannelInboundHandler与ChannelOutboundHandler,故它的inbound与outbound都返回true。

代码@2:Unsafe,Netty操作类。

[](()2.3.2 handlerAdded、handlerRemoved

public void handlerAdded(ChannelHandlerContext ctx) throws Exception {

}

public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {

}

ChanelHandler增加与移除事件处理逻辑:不做任何处理。为什么可以不传播呢?其实上文在讲解addFirst方法时已提到,在添加一个ChannelHandler到事件链时,会根据通道是否被注册,如果未注册,会先阻塞执行,DefaultChannelPipeline会保存一条执行链,等通道被注册后处触发执行,HeadContext作为一个非业务类型的事件处理器,对通道的增加与否无需关注。

[](()2.3.3 exceptionCaught

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

ctx.fireExceptionCaught(cause);

}

通道异常处理事件的处理逻辑:HeadContext的选择是自己不关注,直接将异常事件往下传播。

[](()2.3.4 channelRegistered

public void channelRegistered(ChannelHandlerContext ctx) throws Exception {

invokeHandlerAddedIfNeeded();

ctx.fireChannelRegistered();

}

final void invokeHandlerAddedIfNeeded() {

assert channel.eventLoop().inEventLoop();

if (firstRegistration) {

firstRegistration = false;

// We are now registered to the EventLoop. It’s time to call the callbacks for the ChannelHandlers,

// that were added before the registration was done.

callHandlerAddedForAllHandlers();

}

}

通道注册事件处理逻辑:当通道成功注册后,判断是否是第一次注册,如果是第一次注册的话,调用所有的ChannelHandler#handlerAdd事件,因为当通道增加到事件链后,如果该通道还未注册,channelAdd事件不会马上执行,需要等通道注册后才执行,故在这里首先需要执行完挂起(延迟等待的任务)。然后调用fireChannelRegistered沿着事件链传播通道注册成功事件。

[](()2.3.5 channelUnregistered

public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {

ctx.fireChannelUnregistered();

// Remove all handlers sequentially if channel is closed and unregistered.

if (!channel.isOpen()) {

destroy();

}

}

通道取消注册事件处理逻辑:首先传播事件,然后判断通道的状态,如果是处于关闭状态(通道调用了close方法),则需要移除所有的ChannelHandler。

[](()2.3.6 channelActive

public void channelActive(ChannelHandlerContext ctx) throws Exception {

ctx.fireChannelActive();

readIfIsAutoRead();

}

通道激活事件的处理逻辑(TCP连接建立成功后触发):首先传播该事件,如果开启自动读机制(autoRead为true),则调用Channel#read方法,向NIO Selector注册读事件。

[](()2.3.7 channelInactive

public void channelInactive(ChannelHandlerContext ctx) throws Exception {

ctx.fireChannelInactive();

}

通道非激活事件处理逻辑:只传播事件。

[](()2.3.8 channelRead

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

ctx.fireChannelRead(msg);

}

通道读事件处理逻辑:向下传播事件,各个编码器、业务处理器将各自处理业务逻辑。

[](()2.3.9 channelReadComplete

public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

ctx.fireChannelReadComplete();

readIfIsAutoRead();

}

通道读完成事件,首先先传播事件,然后如果开启了自动读取的话,继续注册读事件。

[](()2.3.10 userEventTriggered

public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

ctx.fireUserEventTriggered(evt);

}

用户自定义事件的处理逻辑:传播事件。

[](()2.3.11 channelWritabilityChanged

public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {

ctx.fireChannelWritabilityChanged();

}

通道可写状态变更事件的处理逻辑:传播事件。

接下来介绍HeadContext对于ChannelOutboundHander事件的处理逻辑:

[](()2.3.12 bind

public void bind( ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)

throws Exception {

unsafe.bind(localAddress, promise);

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值