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調用過程,後面的章節咱們會作詳細分析。

相關文章
相關標籤/搜索