Netty4.x 源码实战系列(四):Pipeline全剖析

在上一篇《Netty4.x 源码实战系列(三):NioServerSocketChannel全剖析》中,咱们详细分析了NioServerSocketChannel的初始化过程,并得出了以下结论:java

在netty中,每个channel都有一个pipeline对象,而且其内部本质上就是一个双向链表

本篇咱们将深刻Pipeline源码内部,对其一探究竟,给你们一个全方位解析。segmentfault

Pipeline初始化过程分析

在上一篇中,咱们得知channel中的pipeline其实就是DefaultChannelPipeline的实例,首先咱们先看看DefaultChannelPipeline的类继承结构图:
图片描述ide

根据类继承结构图,咱们看到DefaultChannelPipeline实现了 ChannelInboundInvoker及ChannelOutboundInvoker两个接口。
顾名思义,一个是处理通道的inbound事件调用器,另外一个是处理通道的outbound事件调用器。oop

inbound: 本质上就是执行I/O线程将从外部read到的数据 传递给 业务线程的一个过程。
outbound: 本质上就是业务线程 将数据 传递给I/O线程, 直至发送给外部的一个过程。this

以下图所示:
图片描述spa

咱们再回到DefaultChannelPipeline这个类,看看其构造方法:线程

protected DefaultChannelPipeline(Channel channel) {
    // 通道绑定channel对象
    this.channel = ObjectUtil.checkNotNull(channel, "channel");
    succeededFuture = new SucceededChannelFuture(channel, null);
    voidPromise =  new VoidChannelPromise(channel, true);
    
    // 初始化头、尾上下文
    tail = new TailContext(this);
    head = new HeadContext(this);

    // 头尾相互连接,造成双向链表
    head.next = tail;
    tail.prev = head;
}

此构造方法主要作了三件事:
一、绑定了当前NioServerSocketChannel实例
二、初始化pipeline双向链表的头、尾节点代理

关于NioServerSocketChannel,我在前一篇中已经作过详细描述,如今咱们着重看看head及tail这两个属性。netty

从上面的构造方法得知,head是HeadContext的实例,tail是TailContext的实例,HeadContext与TailContext都是DefaultChannelPipeline的内部类,它们的类继承结构图以下:code

HeadContext类继承结构图
图片描述

TailContext类继承结构图
图片描述

从类继承图咱们能够看出:
一、HeadContext与TailContext都是通道的handler(中文通常叫作处理器)
二、HeadContext既能够用于outbound过程的handler,也能够用于inbound过程的handler (关于inboun和outbound上面已经做了解释)
三、TailContext只能够用于inbound过程的handler
四、HeadContext 与 TailContext 同时也是一个处理器上下文对象

下面我将以HeadContext为例,看看它初始化过程当中到底做了哪些工做

head = new HeadContext(this);

在DefaultChannelPipeline的构造方法中,咱们看到head结点初始化代码如上面所示,对应构造器代码以下:

HeadContext(DefaultChannelPipeline pipeline) {
    super(pipeline, null, HEAD_NAME, false, true);
    unsafe = pipeline.channel().unsafe();
    setAddComplete();
}

在其内部,它会继续调用父类AbstractChannelHandlerContext的构造器

AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor, String name,
                                  boolean inbound, boolean outbound) {
        this.name = ObjectUtil.checkNotNull(name, "name");
        this.pipeline = pipeline;
        this.executor = executor;
        this.inbound = inbound;
        this.outbound = outbound;
        
        ordered = executor == null || executor instanceof OrderedEventExecutor;
    }

此构造方法,只是设置了当前context对象对应的Pipeline以及此context是做用于outbound。
AbstractChannelHandlerContext类还有另外两个额外属性,他们是实现双向链表的关键:

volatile AbstractChannelHandlerContext next;  // 指定下一个结点
volatile AbstractChannelHandlerContext prev;  // 指定前一个结点

HeadContext 同时还绑定了unsafe对象,咱们再回顾一下unsafe对象。

咱们从上一篇已经得知 unsafe其实就是对java nio 通道底层调用进行的封装,就至关于一个代理类对象。

而DefaultChannelPipeline初始化时,已经绑定了channel,且因为是服务端,因此此channel是NioServerSocketChannel

protected DefaultChannelPipeline(Channel channel) {
    // 通道绑定channel对象
    this.channel = ObjectUtil.checkNotNull(channel, "channel");
    
    ... //非相关代码已省略
}

因此

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

就是

unsafe = new NioMessageUnsafe();

关于pipeline的tail结点初始化过程跟head差很少,这里就不做赘述了。

阶段性总结:
一、每一个channel初始化时,都会建立一个与之对应的pipeline;
二、此pipeline内部就是一个双向链表;
三、双向链表的头结点是处理outbound过程的handler,尾节点是处理inbound过程的handler;
四、双向链表的结点同时仍是handler上下文对象;

Pipeline在服务端bind过程当中的应用

经过《Netty4.x 源码实战系列(二):服务端bind流程详解》 一文,咱们知道,服务端channel在初始化过程当中,会调用addLast方法,并传递了一个ChannelInitializer对象

@Override
void init(Channel channel) throws Exception {
    
    // 非相关代码已省略
    ChannelPipeline p = channel.pipeline();
    
    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(final Channel ch) throws Exception {
            final ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = config.handler();
            if (handler != null) {
                pipeline.addLast(handler);
            }

            ch.eventLoop().execute(new Runnable() {
                @Override
                public void run() {
                    pipeline.addLast(new ServerBootstrapAcceptor(
                            ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                }
            });
        }
    });
}

本节咱们将详细分析一下addLast的过程 与 ChannelInitializer。

ChannelInitializer
咱们先看一下ChannelInitializer的类继承结构图
图片描述

经过类继承图,咱们得知ChannelInitializer的匿名对象其实就是一个处理inbound过程的处理器,与pipeline中的tail同样,目前稍有不一样的就是ChannelInitializer的匿名对象并非一个context对象。

关于ChannelInitializer匿名对象的initChannel方法实现的内容,本篇先不做详述,当讲到EventLoop时,咱们再来回顾一下。

pipeline.addLast方法

addLast具体实现以下:

@Override
public final ChannelPipeline addLast(ChannelHandler... handlers) {
    return addLast(null, handlers);
}

经过此方法参数,咱们也能够得出,init(Channel channel)方法中的addLast其实就是想Pipeline中添加一个处理器。
addLast内部继续调用另外一个重载方法:

@Override
public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
    if (handlers == null) {
        throw new NullPointerException("handlers");
    }

    for (ChannelHandler h: handlers) {
        if (h == null) {
            break;
        }
        addLast(executor, null, h);
    }

    return this;
}

最终调用的是下面的重载方法(已省略非相关代码):

@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
        checkMultiplicity(handler);

        newCtx = newContext(group, filterName(name, handler), handler);

        addLast0(newCtx);

        
    }
    
    return this;
}

newContext方法的做用就是对传入的handler进行包装,最后返回一个绑定了handler的context对象:

private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
    return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
}

新的对象是DefaultChannelHandlerContext类的实例。

接着咱们再看看addLast0方法

private void addLast0(AbstractChannelHandlerContext newCtx) {
    AbstractChannelHandlerContext prev = tail.prev;
    newCtx.prev = prev;
    newCtx.next = tail;
    prev.next = newCtx;
    tail.prev = newCtx;
}

通过addLast0,新包装的context已经添加至pipeline中了,此时的pipeline结果变化过程以下:

从addLast0代码片断得知, 每一个新添加的结点,都是从tail结点以前插入

图片描述

图片描述

本篇总结:
通过本篇的代码研究,对于Pipeline得出如下结论:
一、channel初始化时,会同时建立一个与之对应的pipeline;
二、此pipeline本质上是一个handler处理器双向链表, 用于将处理inbound及outbound过程的handler都串联起来;
三、在netty中,对于I/O处理分为两种流向,对于获取外部数据资源进行处理的,都是对应inbound,好比read等,而对于向外部发送数据资源的,都对于outbound,好比connetct及write等。

关于pipeline中的handler调用过程,后面的章节咱们会作详细分析。

相关文章
相关标签/搜索