在netty中有一个与我们业务开发很重要的知识点,那就是pipeline,也就是管道。对于每一个客户端的链接,在netty服务端中都会针对每一个客户端channel生成一个pipeline,而一个pipeline中又会包含各种的handler,而我们的业务代码大多数就是基于这些handler上去编写的,但是关于整个pipeline的一些细节,里面的一些设计都是很精妙的,下面我们就来详细讲一讲这一块
Pipeline的内部结构
这里我们先从图上大概看一下pipeline的内部结构。首先每一个pipeline在初始化的时候就会有两个默认的handler,分别是HeadContext和TailContext这两个handler,其余添加的handler都是在这两个handler之间形成的handler链,并且通过上图可知,其实pipeline内部是会把每一个handler都包装用一个ChannelHandlerContext包装起来,具体的实现是DefaultChannelHandlerContext,而每一个ChannelHandlerContext中又会有两个指针,分别是pre和next,这两个指针就是用来构造handler链表的,使得handler能够进行方法的链式调用
Pipeline中如何发起一个channel事件的传递?
可以看到ChannelHandlerContext具体有一个抽象实现AbstractChannelHandlerContext,而AbstractChannelHandlerContext的默认实现是DefaultChannelHandlerContext,在DefaultChannelHandlerContext中就有一个handler的成员变量,也就是上图中所包装的handler。那么这个ChannelHandlerContext的作用到底是干嘛的呢?netty在这里设计的它的作用是什么呢?其实handler的某一个事件方法进行链式调用的时候,具体链式调用就是靠ChannelHandlerContext实现的,因为毕竟我们可以看到pre和next这两个指针是在AbstractChannelHandlerContext中,那么我们下面就具体看下ChannelHandlerContext是如何实现handler方法的链式调用的,在pipeline中有很多fireChannelXXX的方法,这些方法分别对应着每一个事件从head节点的handler事件链式调用,如下图:
我们以handler中其中一个事件方法channelRegistered为例:
如果我们想要在这个pipeline中传递channelRegistered这个事件,那么就需要调用pipeline的fireChannelRegistered方法,该方法就可以从head节点开始对channelRegistered这个事件进行handler的链式调用了,我们点进去看一下
入参是指定的一个handler,也就是说该方法的作用就是从指定的handler开始进行handler事件的链式调用。这里注意了,这里是一个静态方法,并且对于每一个事件来说都会有类似的静态方法,如下图:
在这些invokeChannelXXX方法中,都会去执行传入参数对应的ctx的invokeChannelXXX方法,方法如下:
而在invokeChannelXXX方法中,会去调用具体这个ctx所包装的那个handler的channelXXX方法
Handler之间如何实现传递channel事件
在上面我们分析到了在pipeline中如何开始发起一个channel事件的传递,那么当一个handler处理完这个事件方法的时候,它又是如何传递这个事件给下一个handler的呢?通常我们都会调用handler的fireChannelXXX方法,如下图:
调用该方法之后就可以出发下一个handler对应的XXX事件方法了,所以我们就来看一下这个fireChannelXXX方法是如何实现传递channel事件给下一个handler,以channelRegistered这个事件为例:
可以看到在fireChannelRegistered方法中,里面又调用了invokeChannelRegistered方法,这个方法上面讲过就是传入一个指定的handler实例,然后调用该handler实例的channelRegistered方法,那么findContextInbound这个方法肯定就是返回一个handler实例了,那么这里就有疑问了,不是有个next指针吗,直接通过这个next指针找到下一个handler不就可以了吗?其实这是因为在netty中handler有inbound和outbond两种,当这个事件是属于inbound类型的,那么就只能在inbound类型的handler之间进行传递,所以这里就不能简单地通过next指针直接获取到下一个handler实例,下面我们具体看下findContextInbound这个方法:
可以看到该方法就是通过一个do...while循环去找到一个合适的handler并返回,那么什么条件算是合适呢?关键就在于skipContext方法
该方法比较复杂,具体解释就看上面的注释吧,而上面的注释中有一个需要解释的点就是mask是什么?所以我们下面先来解释下什么是handler的mask标记
Handler的mask标记
在AbstarctChannelHandlerContext的构造方法中会调用一个mask方法,并且每一个handler都会有一个executionMask属性,该属性就是通过mask方法返回的
mask方法比较长,这里我就只截图一部分出来了,后面的代码基本都是差不多的。通过上图的注释我们可以得出handler的mask标记其实就是通过一个二进制的数去标记出该handler的每一个事件方法是否需要被传递调用,该二进制的每一位就代表对应的事件方法,如果是0就表示对应的事件方法不需要被传递调用,如果是1就表示对应的事件方法需要被传递调用,那么用户在自定义这个handler的时候是如何决定这个每一个事件方法是否需要被传递调用呢?答案就是通过@Skip注解,如果这个handler的某个事件方法加了该注解,那么当pipeline传递这个事件的时候,该handler的这个事件方法就不会被调用到。看到这里我们再去回头看下fireChannelRegistered方法
static final int MASK_CHANNEL_REGISTERED = 1 << 1;
所以findContextInbound也就是说表示从当前handler开始往后寻找inbound类型的,并且channelRegistered方法没有加@Skip注解的handler,得到handler之后就传到invokeChannelRegistered方法中,在invokeChannelRegistered方法中再调用这个handler的channelRegistered方法,这样就实现了channelRegistered方法的链式调用了