1. 程式人生 > >Netty4.x 原始碼實戰系列(四):深入淺出學Pipeline

Netty4.x 原始碼實戰系列(四):深入淺出學Pipeline

在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<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得出以下結論:

1、channel初始化時,會同時建立一個與之對應的pipeline;
2、此pipeline本質上是一個handler處理器雙向連結串列, 用於將處理inbound及outbound過程的handler都串聯起來;
3、在netty中,對於I/O處理分為兩種流向,對於獲取外部資料資源進行處理的,都是對應inbound,比如read等,而對於向外部發送資料資源的,都對於outbound,比如connetct及write等。

關於pipeline中的handler呼叫過程,後面的章節我們會做詳細分析。

相關推薦

Netty4.x 原始碼實戰系列深入淺出Pipeline

在netty中,每一個channel都有一個pipeline物件,並且其內部本質上就是一個雙向連結串列 本篇我們將深入Pipeline原始碼內部,對其一探究竟,給大家一個全方位解析。 Pipeline初始化過程分析 在上一篇中,我們得知ch

Netty4.x 原始碼實戰系列NioServerSocketChannel全剖析

根據上一篇《Netty4.x 原始碼實戰系列(二):服務端bind流程詳解》所述,在進行服務端開發時,必須通過ServerBootstrap引導類的channel方法來指定channel型別, channel方法的呼叫其實就是例項化了一個用於生成此channel

Netty4.x 原始碼實戰系列服務端bind流程詳解

在上一篇《ServerBootstrap 與 Bootstrap 初探》中,我們已經初步的瞭解了ServerBootstrap是netty進行服務端開發的引導類。 且在上一篇的服務端示例中,我們也看到了,在使用netty進行網路程式設計時,我們是通過bind方法

Spring5原始碼分析系列Spring5原始碼分析2

本文緊接上文Spring5原始碼分析1,講解基於XML的依賴注入,文章參考自Tom老師視訊,下一篇文章將介紹基於Annotation的依賴注入。 基於XML的依賴注入 1、依賴注入發生的時間 當SpringIOC容器完成了Bean定義資源的定位、載入和解析註冊以後,IO

py-faster-rcnn原始碼解讀系列——anchor_target_layer.py

本文介紹了在solver中出現的用python定義的layer,顧名思義,該layer主要功能是產生anchor,並對anchor進行評分等操作,詳細見程式碼註釋。 class AnchorTargetLayer(caffe.Layer): """ As

Spring5原始碼分析系列IOC容器

本章開始進入Spring5原始碼分析,文章有點長,參考自Tom老師視訊。 什麼是IOC/DI? IOC(InversionofControl)控制反轉:所謂控制反轉,就是把原先我們程式碼裡面需要實現的物件建立、依賴的程式碼,反轉給容器來幫忙實現。那麼必然的我們需要建立一個

羅斯基白話TensorFlow+實戰系列變數管理

白話TensorFlow +實戰系列(一)變數管理        這篇文章主要記錄常用的兩種管理變數的方法。個人感覺變數管理是比較重要的,特別是當建立一個複雜的神經網路的時候,變數一旦增多,如果不

基於 abp vNext 和 .NET Core 開發部落格專案 - Blazor 實戰系列

## 系列文章 1. **[基於 abp vNext 和 .NET Core 開發部落格專案 - 使用 abp cli 搭建專案](https://www.cnblogs.com/meowv/p/12896177.html)** 2. **[基於 abp vNext 和 .NET Core 開發部落格專案

Spring Boot系列Spring Boot原始碼解析

一、自動裝配原理   之前博文已經講過,@SpringBootApplication繼承了@EnableAutoConfiguration,該註解匯入了AutoConfigurationImport Selector,這個類主要是掃描spring-boot-autoconfigure下面的META-INF\

物聯網平臺構架系列 Amazon, Microsoft, IBM IoT 平臺導論 之 平臺

物聯網; iot; aws; 亞馬遜; greengrass;microsoft; azure;ibm; watson; bluemix最近研究了一些物聯網平臺技術資料,以做選型參考。腦子裏積累大量信息,便想寫出來做一些普及。作為科普文章,力爭通俗易懂,不確保概念嚴謹性。我會給考據癖者提供相關英文鏈接,以便深

搜索引擎ElasticSearch系列 ElasticSearch2.4.4 sql插件安裝

china code als 插件 技術分享 -s fun nlp 4.0 一:ElasticSearch sql插件簡介   With this plugin you can query elasticsearch using familiar SQL syntax.

Python爬蟲系列Beautiful Soup解析HTML之把HTML轉成Python對象

調用 nor 結束 版本 現在 name屬性 data 官方文檔 get 在前幾篇文章,我們學會了如何獲取html文檔內容,就是從url下載網頁。今天開始,我們將討論如何將html轉成python對象,用python代碼對文檔進行分析。 (牛小妹在學校折騰了好幾天,也沒把h

JavaScript難點系列作用域

文章 this 的確 空間 console 知識點 人的 歧義 查找 深入了解js這門語言後,才發現它有著諸多眾所周知的難點(例如:閉包、原型鏈、內存空間等)。有的是因為js的設計缺陷導致的,而有的則是js的優點。不管如何,總需要去學會它們,在學習過程中我覺得只看別人的文章

構建NetCore應用框架之實戰BitAdminCore框架1.0登錄功能細化及技術選型

1.0 dmi 也會 繼承 blank bit 技術選型 cor 我會 本篇承接上篇內容,如果你不小心點擊進來,建議從第一篇開始完整閱讀,文章內容繼承性連貫性。 構建NetCore應用框架之實戰篇系列 一、BitAdminCore框架1.0版本 1、1.0版本是指

Django系列多表操作

例如 get city 定義 庫類 修改配置 銷售部 blog 返回 1、創建模型 例:我們來假定下面這些概念,字段和關系   作者模型:一個作者有姓名和年齡。   作者詳細模型:把作者的詳情放到詳情表,包含生日,手機號,家庭住址等信息。作者詳情模型和作者模型之間是一對一(

linux系列mkdir命令

1、命令格式:   mkdir [選項] 目錄名 2、命令功能:   通過 mkdir 命令可以實現在指定位置建立以 DirName(指定的檔名)命名的資料夾或目錄。要建立資料夾或目錄的使用者必須對所建立的資料夾的父資料夾具有寫許可權。並且,所建立的資料夾(目錄)

Docker系列容器之間的網路通訊

首先我們需要知道:兩個容器要能通訊,必須要有屬於同一個網路的網絡卡。 先來正常情況下我們的容器預設是否是能通訊的,這裡執行兩個測試容器: docker run -it --name=bbox1 busybox docker run -it --name=bbox2 busybox 然後我們進入bb

從0到1使用Kubernetes系列搭建第一個應用程式

上一篇文章《從0到1使用Kubernetes系列(三):使用Ansible安裝Kubernetes叢集》中,我們搭建了一套Kubernetes叢集,接下來將在本文中介紹如何使用Kubernetes部署一個Nginx並通過Pod IP、Service IP、Ingress這三種方式訪問Nginx。 傳統Kube

WebAssembly 系列WebAssembly 工作原理

WebAssembly 是除了 JavaScript 以外,另一種可以在網頁中執行的程式語言。過去如果你想在瀏覽器中執行程式碼來對網頁中各種元素進行控制,只有 JavaScript 這一種選擇。 所以當人們談論 WebAssembly 的時候,往往會拿 JavaScript 來進行比較。但

STM32開發筆記50STM32F4+DP83848乙太網通訊指南系列PHY配置

本章為系列指南的第四章,這一章將正式進入乙太網的配置和使用。首先我們關注一下PHY的配置,前面講到,我們的工程使用了開發板上的一顆DP83848晶片。 RMII和ADDR的確定 接下來我們來看開發板的原理圖: 通過電路原理圖可以看到接線方式是使用RMII介面模式接線的,因此接下來我