Netty的Handler链调用机制及如何组织详解
作者:WARRIOR30
什么是 Handler
Netty是一款基于NIO的异步事件驱动网络应用框架,其核心概念之一就是Handler。而Handler是Netty中处理事件的核心组件,用于处理入站和出站的数据流,实现业务逻辑和网络协议的处理。
在Netty中,Handler是一个接口,主要分为两种:ChannelInboundHandler
(入站Handler)和ChannelOutBoundHandler
(出站Handler),如下图所示。
ChannelInboundHandler
:处理从网络通道中读取到的数据,包括解码、反序列化、消息分发等操作;ChannelOutboundHandler
:可以负责将处理结果编码、加密并通过网络通道发送出去等
Handler 是怎么被组织起来的
- 为了方便事件在各个Handler中处理与传递,在Netty中,每一个
ChannelHandler
被封装为一个ChannelHandlerContext
ChannelHandlerContext
提供了对ChannelHandler
的访问,以及它前后相邻的ChannelHandler
的访问。在ChannelHandlerContext
的抽象实现类AbstractChannelHandlerContext
,可以很清楚的看到,它拥有next
和prev
两个属性,分别对应下一个和上一个ChannelHandlerContext
- 在Netty中,一个完整的处理链路可以由多个
ChannelHandlerContext
组成,这些ChannelHandlerContext
形成一个管道(Pipeline) ,通过管道串联起来,形成完整的数据处理流程。在数据流经过管道中的每个ChannelHandlerContext
时,都可以对数据进行一些特定的处理。
Handler 链调用机制
简述
在ChannelPipeline
的源码中,我们可以看到这样的一段注释
这可能容易让人产生认为Pipeline
中维护了两条链表,其中一条用于处理出站事件,另外一条处理入站事件。
实际上,Pipeline
是维护了一条双向链表,当数据从入站方向流经处理程序链时,数据从双向链表的head
向后面遍历,依次将事件交由后面一个handler
处理,当数据从出站方向流经处理程序链时,数据从双向链表的tail
向前面遍历,依次将事件交由下一个handler
处理。
ChannelPipeline
如何调度 handler
上面提到,Pipeline
中维护了一个由handler
双向链表。那么,当事件进入pipeline
中时,ChannelPipeline
是如何调用这些 handler
的呢 ?
- 当一个请求进入时,
pipeline
会首先调用ChannelContext
的fireXXX()
方法(下面以fireChannelRead()
为例),在fireChannelRead()
中,会调用invokeChannelRead(head, msg)
并将包装着下一个要执行的handler
的ChannelContext
传入
事件第一个经过的一定是 head
,因此在下面的代码中,invokeChannelRead()
传入的是 head
@Override public final ChannelPipeline fireChannelRead(Object msg) { AbstractChannelHandlerContext.invokeChannelRead(head, msg); return this; }
- 进入
invokeChannelRead()
,后会调用handler
真正的channelRead(this, msg)
方法进行业务处理
private void invokeChannelRead(Object msg) { if (invokeHandler()) { try { ((ChannelInboundHandler) handler()).channelRead(this, msg); } catch (Throwable t) { notifyHandlerException(t); } } else { fireChannelRead(msg); } }
- 进行业务处理之后,
channelRead()
会执行ctx.fireChannelRead(msg)
,通过这行代码将处理过的消息传递给下一个处理器进行处理
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ctx.fireChannelRead(msg); }
- 从下面的代码可以看到,
fireChannelRead()
方法与上面 1 中唯一不同的是,调用了findContextInbound()
方法来寻找下一个 handler - 在
findContextInbound()
中,我们可以发现,它使用了一个do while
循环来寻找下一个handler
,这个循环当下一个handler
的类型同为inbound
时,会被返回。因此,当事件入站时,每次进行事件处理的handler
都是ChannelInboundHandler
。(出站同理) - 至此,
fireChannelRead()
调用当前AbstractChannelHandlerContext
的invokeChannelRead()
回到 2
@Override 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; }
从这点可以看出:一般情况下,我们需要在处理程序链中的每个handler
调用 ctx.fireChannelRead(msg)
,以确保将事件传递给下一个处理程序。如果在handler
中未调用 ctx.fireChannelRead(msg)
,则该事件将被截获并停留在当前handler
中,不会传递到下一个处理程序。
事件出站的调度从双向链表的tail
开始,调用机制与入站类似,这里不再赘述。
以上就是Netty的Handler链调用机制及如何组织详解的详细内容,更多关于Netty Handler链调用的资料请关注脚本之家其它相关文章!