资讯专栏INFORMATION COLUMN

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

13651657101 / 2443人阅读

摘要:在上一篇源码实战系列三全剖析中,我们详细分析了的初始化过程,并得出了如下结论在中,每一个都有一个对象,并且其内部本质上就是一个双向链表本篇我们将深入源码内部,对其一探究竟,给大家一个全方位解析。

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

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

本篇我们将深入Pipeline源码内部,对其一探究竟,给大家一个全方位解析。

Pipeline初始化过程分析

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

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

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

如下图所示:

我们再回到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;
}

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

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

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

HeadContext类继承结构图

TailContext类继承结构图

从类继承图我们可以看出:
1、HeadContext与TailContext都是通道的handler(中文一般叫做处理器)
2、HeadContext既可以用于outbound过程的handler,也可以用于inbound过程的handler (关于inboun和outbound上面已经作了解释)
3、TailContext只可以用于inbound过程的handler
4、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差不多,这里就不作赘述了。

阶段性总结: 
1、每个channel初始化时,都会创建一个与之对应的pipeline;
2、此pipeline内部就是一个双向链表;
3、双向链表的头结点是处理outbound过程的handler,尾节点是处理inbound过程的handler;
4、双向链表的结点同时还是handler上下文对象;
Pipeline在服务端bind过程中的应用

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

@Override
void init(Channel channel) throws Exception {
    
    // 非相关代码已省略
    ChannelPipeline p = channel.pipeline();
    
    p.addLast(new ChannelInitializer() {
        @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得出以下结论:
1、channel初始化时,会同时创建一个与之对应的pipeline;
2、此pipeline本质上是一个handler处理器双向链表, 用于将处理inbound及outbound过程的handler都串联起来;
3、在netty中,对于I/O处理分为两种流向,对于获取外部数据资源进行处理的,都是对应inbound,比如read等,而对于向外部发送数据资源的,都对于outbound,比如connetct及write等。

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68101.html

相关文章

  • Netty4.x 源码实战系列(二):服务端bind流程详解

    摘要:对于,目前大家只知道是个线程组,其内部到底如何实现的,它的作用到底是什么,大家也都不太清楚,由于篇幅原因,这里不作详细介绍,后面会有文章作专门详解。 在上一篇《ServerBootstrap 与 Bootstrap 初探》中,我们已经初步的了解了ServerBootstrap是netty进行服务端开发的引导类。 且在上一篇的服务端示例中,我们也看到了,在使用netty进行网络编程时,我...

    laoLiueizo 评论0 收藏0
  • Netty4.x 源码实战系列(三):NioServerSocketChannel剖析

    摘要:本篇将通过实例化过程,来深入剖析。及初始化完成后,它们会相互连接。我们在回到的构造方法父类构造方法调用完成后,还要初始化一下自己的配置对象是的内部类而又是继承自,通过代码分析,此对象就是就会对底层一些配置设置行为的封装。 根据上一篇《Netty4.x 源码实战系列(二):服务端bind流程详解》所述,在进行服务端开发时,必须通过ServerBootstrap引导类的channel方法来...

    Flink_China 评论0 收藏0
  • Netty4.x 源码实战系列(一):ServerBootstrap 与 Bootstrap 初探

    摘要:而用于主线程池的属性都定义在中本篇只是简单介绍了一下引导类的配置属性,下一篇我将详细介绍服务端引导类的过程分析。 从Java1.4开始, Java引入了non-blocking IO,简称NIO。NIO与传统socket最大的不同就是引入了Channel和多路复用selector的概念。传统的socket是基于stream的,它是单向的,有InputStream表示read和Outpu...

    BakerJ 评论0 收藏0
  • Netty4.x 源码实战系列(五):深入浅出学NioEventLoopGroup

    摘要:接下来的两篇文章,我将从源码角度为大家深入浅出的剖析的线程模型工作机制。我们看一下的源码通过的代码发现,实现了接口,其内部会通过指定的默认线程工厂来创建线程,并执行相应的任务。至此,初始化完成了。下一篇我们将详细介绍,敬请期待。 我们都知道Netty的线程模型是基于React的线程模型,并且我们都知道Netty是一个高性能的NIO框架,那么其线程模型必定是它的重要贡献之一。 在使用ne...

    MSchumi 评论0 收藏0
  • 【自己读源码Netty4.X系列(一) 启动类概览

    摘要:一些想法这个系列想开很久了,自己使用也有一段时间了,利用也编写了一个简单的框架,并运用到工作中了,感觉还不错,趁着这段时间工作不是很忙,来分析一波源码,提升下技术硬实力。 一些想法 这个系列想开很久了,自己使用netty也有一段时间了,利用netty也编写了一个简单的框架,并运用到工作中了,感觉还不错,趁着这段时间工作不是很忙,来分析一波源码,提升下技术硬实力。 结构 这里先看下net...

    qingshanli1988 评论0 收藏0

发表评论

0条评论

13651657101

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<