Netty核心组件源码说明

Pipeline源码说明

首先我们先来说一下Netty中三个非常重要的组件:ChannelPipeline、ChannelHandler、ChannelHandlerContext

三者关系(如下图所示):

(1)每当一个ServerSocket创建一个新的连接,就会创建一个Scoket,对应的就是目标客户端。
(2)每一个新创建的Socket都将会分配一个全新的ChannelPipeline
(3)每一个ChannelPipeline内部都含有多个ChannelHandlerContext
(4)他们一起组成了双向链表,这些Context用于包装我们调用的addlast()方法时添加的ChannelHandler
在这里插入图片描述

(1)上图中:ChannelSocket和ChannelPipeline是一对一的关联关系,而pipeline内部的多个context形成了链表,Context只是对Handler的封装。
(2)当一个请求进来的时候,会进入Socket对应的pipeline,并经过所有的handler,这就是设计模式中的过滤器模式。

ChannelPipeline的设计:

如上图所示:ChannelPipeline类是对多个handler的动态管理,所以这个类本身相当于一个双向链表,可以动态的增加删除或者替换被ChannelHanlerContext包装的ChannelHandler对象,看其源码可得。ChannelPipeline本身是一个接口,主要函数就是对ChannelHandler对象的动态管理。其实现了ChannelInboundInvoker, ChannelOutboundInvoker, Iterable接口,表示可以调用数据出站的方法和入站的方法,并且可以遍历链表,因为Iterable一个很重要的方法就是foreach。所以ChannelPipeline类似LinkedList。且可以返回Channel(也就是socket)
在这里插入图片描述
在这里插入图片描述
在此基础上,有一个很重要的问题需要说明,ChannelPipeline中的handler是分I/O两类,但是我们添加的时候并不会做区分,而是一股脑的没有顺序全部添加到pipeline中,比如这样一个pipeline:编码器–>解码器–>自定义handler,那么这3个handler相当于处于一个双向链表中,在具体执行过程中,肯定是沿着我们管线进行,那么我们是怎么来区分inboundHandler和outboundHandler呢???

我们先来讲述设计,再来看源码的设计
1、首先我们所有的Handler是被ChannelHandlerContext包装的,我们在实现具体的Handler时是根据inbound模板或者outbound模板来实现的,在 ChannelHandler 被添加进 pipeline 的时候,Netty 会根据当前 ChannelHandler 的类型以及其覆盖实现的异步事件回调方法,通过 | 运算 向 ChannelHandlerContext#executionMask 字段添加该 ChannelHandler 的执行资格,也就是说明了inbound或outbound。

final class ChannelHandlerMask {
    
    
    ....................省略......................
 
    static final int MASK_CHANNEL_ACTIVE = 1 << 3;
    static final int MASK_CHANNEL_READ = 1 << 5;
    static final int MASK_CHANNEL_READ_COMPLETE = 1 << 6;
    static final int MASK_WRITE = 1 << 15;
    static final int MASK_FLUSH = 1 << 16;
 
   //outbound事件掩码集合
   static final int MASK_ONLY_OUTBOUND =  MASK_BIND | MASK_CONNECT | MASK_DISCONNECT |
            MASK_CLOSE | MASK_DEREGISTER | MASK_READ | MASK_WRITE | MASK_FLUSH;
    ....................省略......................
}

2、此时我们的pipeline中的所有handler都有了inbound的属性或者outbound的属性,那么我们在执行过程中是怎么查找下一个要执行的handler呢?
这个问题主要是靠ChannelHandlerContext来解决的,我们看源码可知是最终底层是由findContextOutbound或findContextInbound方法完成,如下所示为该方法源码:findContextOutbound 方法接收的参数是一个掩码,这个掩码表示要向前查找具有什么样执行资格的 ChannelHandler。正如我们前面所讲,每个handler在创建之初就确定了它的执行资格,实际上,其执行资格就是通过掩码来确定的。较早版本的Netty中ChannelHandlerContext中是有inbound和outbound的标志值来确定其IO属性。

  private AbstractChannelHandlerContext findContextOutbound(int mask) {
    
    
        AbstractChannelHandlerContext ctx = this;
        //获取当前ChannelHandler的executor
        EventExecutor currentExecutor = executor();
        do {
    
    
            //获取前一个ChannelHandler
            ctx = ctx.prev;
        } while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_OUTBOUND));
        return ctx;
    }
    //判断前一个ChannelHandler是否具有响应Write事件的资格
    private static boolean skipContext(
            AbstractChannelHandlerContext ctx, EventExecutor currentExecutor, int mask, int onlyMask) {
    
    
 
        return (ctx.executionMask & (onlyMask | mask)) == 0 ||
                (ctx.executor() == currentExecutor && (ctx.executionMask & mask) == 0);
    }

ChannelHandler的设计:

(1)首先ChannelHandler中有两个重要的方法,我们是实现时可以重写这两个方法,其中handlerAdded是在其刚被加入到pipeline中时捕捉,handlerRemoved是在被移除的时候捕捉,我们可以在这两个时刻自定义操作,这个很想Vue中强调的生命周期的概念,我们通过在不同生命周期的时刻来自定义我们的操作来满足我们的需求。
在这里插入图片描述

(2)ChannelHandler的作用就是处理IO事件,并将其转发给下一个处理程序ChannelHandler,Handler的处理事件分入站和出站,两个方向的操作是不同的,因此Netty定义了两个子接口继承ChannelHandler。
(3)ChannelInboundHandler入站事件接口
channelActive用于当前channel处于活动状态时被调用
channelRead当从Channel读取数据时被调用
在这里插入图片描述
(4)ChannelOutboundHandler出站事件接口
bind方法,当请求将Channel绑定到本地地址时调用
close方法,党请求关闭Channel时调用
在这里插入图片描述
(5)那么在pipeline中刚创建时,会有head和tail两个handler,这两个handler的IO属性是怎么样的呢?
如下图所示,HeadContext同时继承了ChannelInboundHandler和ChannelOutboundHandler,所以是同时具备IO属性,而Tail只继承了ChannelInboundHandler,所以只有Inbound属性。
在这里插入图片描述
在这里插入图片描述

ChannelHandlerContext的设计:

(1)我们前面的讲解已经涉及到了不少的ChannelHandlerContext的内容,该接口继承了ChannelInboundInvoker和ChannelOutboundInvoker接口,下图分别是这两个接口的实现方法,这两个invoker就是针对入站和出站方法来的,就是在入站或出站handler的外层再包装一层,达到方法前后拦截并做一写特定的操作的目的。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(2)ChannelHandlerContext不仅仅继承了这两个接口的方法,同时也定义了一些自己的方法,这些方法能够获取Context上下文环境中对应的比如channel、executor、handler、pipeline等。
(3)ChannelHandlerContext就是包装了handler相关的一切,以方便其可以在pipeline中操作handler,而使得handler可以一心进行具体的处理。

猜你喜欢

转载自blog.csdn.net/XZB119211/article/details/127896260