1. 程式人生 > >Netty4.x 原始碼實戰系列(三):NioServerSocketChannel全剖析

Netty4.x 原始碼實戰系列(三):NioServerSocketChannel全剖析

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

本篇將通過NioServerSocketChannel例項化過程,來深入剖析NioServerSocketChannel。

在開始程式碼分析之前,我們先看一下NioServerSocketChannel的類繼承結構圖:
這裡寫圖片描述

呼叫工廠完成NioServerSocketChannel例項的建立

在繫結偵聽埠過程中,我們呼叫了AbstractBootstrap的initAndRegister方法來完成channel的建立與初始化,channel例項化程式碼如下:

channelFactory.newChannel()

而channelFactory物件是我們通過ServerBootstrap.channel方法的呼叫生成的

 public B channel(Class<? extends C> channelClass) {
    if (channelClass == null) {
        throw new NullPointerException("channelClass"
); } return channelFactory(new ReflectiveChannelFactory<C>(channelClass)); }

通過程式碼可知,此工廠物件是ReflectiveChannelFactory例項

public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {

    private final Class<? extends T> clazz;

    public ReflectiveChannelFactory
(Class<? extends T> clazz) {
if (clazz == null) { throw new NullPointerException("clazz"); } this.clazz = clazz; } @Override public T newChannel() { try { return clazz.getConstructor().newInstance(); } catch (Throwable t) { throw new ChannelException("Unable to create Channel from class " + clazz, t); } } }

所以 channelFactory.newChannel() 例項化其實就是NioServerSocketChannel無參構造方法反射而成。

NioServerSocketChannel例項化過程分析

我們先看一下NioServerSocketChannel的無參構造程式碼

public NioServerSocketChannel() {
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}

無參構造方法中有兩個關鍵點:
1、使用預設的多路複用器輔助類 DEFAULT_SELECTOR_PROVIDER

 private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();

2、通過newSocket建立ServerSocketChannel

private static ServerSocketChannel newSocket(SelectorProvider provider) {
    try {

        return provider.openServerSocketChannel();
    } catch (IOException e) {
        throw new ChannelException(
                "Failed to open a server socket.", e);
    }
}

我們將newSocket生成的ServerSocketChannel物件繼續傳遞給本類中的NioServerSocketChannel(ServerSocketChannel channel)構造方法

public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

在其內部,我們會呼叫父類AbstractNioMessageChannel的構造方法:

protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent, ch, readInterestOp);
}

因為是服務端新生成的channel,第一個引數指定為null,表示沒有父channel,第二個引數指定為ServerSocketChannel,第三個引數指定ServerSocketChannel關心的事件型別為SelectionKey.OP_ACCEPT。

在AbstractNioMessageChannel內部會繼續呼叫父類AbstractNioChannel的構造方法:

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    // 繼續呼叫父類構造方法
    super(parent);
    // 將ServerSocketChannel物件儲存
    this.ch = ch;
    // 設定關心的事件
    this.readInterestOp = readInterestOp;
    try {
        // 設定當前通道為非阻塞的
        ch.configureBlocking(false);
    } catch (IOException e) {
        try {
            ch.close();
        } catch (IOException e2) {
            if (logger.isWarnEnabled()) {
                logger.warn(
                        "Failed to close a partially initialized socket.", e2);
            }
        }

        throw new ChannelException("Failed to enter non-blocking mode.", e);
    }
}

在AbstractNioChannel中做了下面幾件事:

1、繼續呼叫父類AbstractChannel(Channel parent)構造方法;
2、通過this.ch = ch 儲存ServerSocketChannel, 因為NioServerSocketChannel是Netty封裝的物件,而ServerSocketChannel是有前面預設selector_provider生成的,是java nio的, 其實“this.ch = ch”可以被認為是繫結java nio服務端通道至netty物件中;
3、設定ServerSocketChannel關心的事件型別;
4、設定ServerSocketChannel為非阻塞的(熟悉Java NIO的都知道如果不設定為false,啟動多路複用器會報異常)

我們再看一下AbstractChannel(Channel parent)的內部程式碼細節

protected AbstractChannel(Channel parent) {
    this.parent = parent;
    id = newId();
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}

此構造方法中,主要做了三件事:

1、給channel生成一個新的id
2、通過newUnsafe初始化channel的unsafe屬性
3、newChannelPipeline初始化channel的pipeline屬性

id的生成我們就不細究了,我們主要看看newUnsafe 及 newChannelPipeline是如何建立unsafe物件及pipeline物件的。

newUnsafe()方法呼叫
在AbstractChannel類中,newUnsafe()是一個抽象方法

protected abstract AbstractUnsafe newUnsafe();

通過上面的類繼承結構圖,我們找到AbstractNioMessageChannel類中有newUnsafe()的實現

@Override
protected AbstractNioUnsafe newUnsafe() {
    return new NioMessageUnsafe();
}

此方法返回一個NioMessageUnsafe例項物件,而NioMessageUnsafe是AbstractNioMessageChannel的內部類

private final class NioMessageUnsafe extends AbstractNioUnsafe {

    private final List<Object> readBuf = new ArrayList<Object>();

    @Override
    public void read() {
        assert eventLoop().inEventLoop();
        final ChannelConfig config = config();
        final ChannelPipeline pipeline = pipeline();
        final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
        allocHandle.reset(config);

        boolean closed = false;
        Throwable exception = null;
        try {
            try {
                do {
                    int localRead = doReadMessages(readBuf);
                    if (localRead == 0) {
                        break;
                    }
                    if (localRead < 0) {
                        closed = true;
                        break;
                    }

                    allocHandle.incMessagesRead(localRead);
                } while (allocHandle.continueReading());
            } catch (Throwable t) {
                exception = t;
            }

            int size = readBuf.size();
            for (int i = 0; i < size; i ++) {
                readPending = false;
                pipeline.fireChannelRead(readBuf.get(i));
            }
            readBuf.clear();
            allocHandle.readComplete();
            pipeline.fireChannelReadComplete();

            if (exception != null) {
                closed = closeOnReadError(exception);

                pipeline.fireExceptionCaught(exception);
            }

            if (closed) {
                inputShutdown = true;
                if (isOpen()) {
                    close(voidPromise());
                }
            }
        } finally {
            if (!readPending && !config.isAutoRead()) {
                removeReadOp();
            }
        }
    }
}

NioMessageUnsafe 只覆蓋了 父類AbstractNioUnsafe中的read方法,通過NioMessageUnsafe 及其父類的程式碼便可以知道, 其實unsafe物件是真正的負責底層channel的連線/讀/寫等操作的,unsafe就好比一個底層channel操作的代理物件。

newChannelPipeline()方法呼叫
newChannelPipeline直接在AbstractChannel內實現

protected DefaultChannelPipeline newChannelPipeline() {
    return new DefaultChannelPipeline(this);
}

該方法返回了建立了一個DefaultChannelPipeline物件

protected DefaultChannelPipeline(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;
}

此DefaultChannelPipeline物件會繫結NioServerSocketChannel物件,並初始化了HeadContext及TailContext物件。

tail = new TailContext(this);
head = new HeadContext(this);

head及tail初始化完成後,它們會相互連線。

通過上面的程式碼可以得出,pipeline就是一個雙向連結串列。關於Pipeline的更多細節,此處不做贅述,歡迎大家關注下一篇文章。

我們在回到NioServerSocketChannel的構造方法 NioServerSocketChannel(ServerSocketChannel channel)

public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

父類構造方法呼叫完成後,NioServerSocketChannel還要初始化一下自己的配置物件

config = new NioServerSocketChannelConfig(this, javaChannel().socket());

NioServerSocketChannelConfig是NioServerSocketChannel的內部類

private final class NioServerSocketChannelConfig extends DefaultServerSocketChannelConfig {
    private NioServerSocketChannelConfig(NioServerSocketChannel channel, ServerSocket javaSocket) {
        super(channel, javaSocket);
    }

    @Override
    protected void autoReadCleared() {
        clearReadPending();
    }
}

而NioServerSocketChannelConfig 又是繼承自DefaultServerSocketChannelConfig,通過程式碼分析,此config物件就是就會對底層ServerSocket一些配置設定行為的封裝。

至此NioServerSocketChannel物件應該建立完成了~

總結:

1、NioServerSocketChannel物件內部綁定了Java NIO建立的ServerSocketChannel物件;
2、Netty中,每個channel都有一個unsafe物件,此物件封裝了Java NIO底層channel的操作細節;
3、Netty中,每個channel都有一個pipeline物件,此物件就是一個雙向連結串列;

相關推薦

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

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

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

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

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

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

mybatis-generator原始碼解讀系列配置讀取

概述:        配置讀取是程式碼生成的基礎工作,主要就是把xml中的元資料讀取到記憶體中,供後面的程式碼生成邏輯使用相關類1、ConfigurationParser功能        主要用來將xml配置檔案讀取到記憶體,獲取根節點,根據根節點的屬性值,選擇對應的子節點

Java 原始碼學習系列——Integer

Integer 類在物件中包裝了一個基本型別 int 的值。Integer 型別的物件包含一個 int 型別的欄位。 此外,該類提供了多個方法,能在 int 型別和 String 型別之間互相轉換,還提供了處理 int 型別時非常有用的其他一些常量和方法。 類定義 publ

MFC原始碼實戰分析——訊息對映原理與訊息路由機制初探

如果在看完上一篇文章後覺得有點暈,不要害怕。本節我們就不用這些巨集,而是用其中的內容重新完成開頭那個程式,進而探究MFC訊息對映的本來面目。 MFC訊息對映機制初探 還我本來面目 class CMyWnd : public CFrameWnd

Spring實戰系列-BeanPostProcessor的妙用

"對於Spring框架,現實公司使用的非常廣泛,但是由於業務的複雜程度不同,瞭解到很多小夥伴們利用Spring開發僅僅是利用了Spring的IOC,即使是AOP也很少用,但是目前的Spring是一個大家族,形成了一個很大的生態,覆蓋了我們平時開發的方方面面,拋開特殊的苛刻要求

安卓系統原始碼編譯系列——常用命令

在下載編譯完成安卓原始碼之後,我們在閱讀、除錯、修改安卓原始碼時,可能還需要對原始碼進行一系列操作,如切換分支、重置等,下面我們就來看看如何對原始碼進行一些常用操作。 模組單獨編譯 1.檢視當前可編譯的所有模組名稱 make modules 2.清除指定模組的編譯

QEMU原始碼分析系列

  從QEMU-0.10.0開始,TCG成為QEMU新的翻譯引擎,使QEMU不再依賴於GCC3.X版本,並且做到了“真正”的動態翻譯(從某種意義上說,舊版本是從編譯後的目標檔案中複製二進位制指令)。TCG的全稱為“Tiny Code Generator”,QEMU的作者Fa

緩沖區溢出實戰教程系列利用OllyDbg了解程序運行機制

成了 代碼段 下界 urn 方便 htm oca 相差 14. 想要進行緩沖區溢出的分析與利用,當然就要懂得程序運行的機制。今天我們就用動態分析神器ollydbg來了解一下在windows下程序是如何運行的。 戳這裏看之前發布的文章: 緩沖區溢出實戰教程系列(一):

Spring系列Spring IoC原始碼解析

一、Spring容器類繼承圖 二、容器前期準備   IoC原始碼解析入口: /** * @desc: ioc原理解析 啟動 * @author: toby * @date: 2019/7/22 22:20 */ public class PrincipleMain { public sta

SpringBoot基礎實戰系列springboot單檔案與多檔案上傳

## springboot單檔案上傳 對於springboot檔案上傳需要了解一個類`MultipartFile `,該類用於檔案上傳。我此次使用`thymeleaf`模板引擎,該模板引擎檔案字尾 `.html`。 #### 1.建立controller ```java /** * 單檔案上傳

基於 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整合Mybatis原始碼解析

一、Mybatis回顧   1、MyBatis介紹   Mybatis是一個半ORM框架,它使用簡單的 XML 或註解用於配置和原始對映,將介面和Java的POJOs(普通的Java 物件)對映成資料庫中的記錄。   2、Mybatis整體架構  二、Spring Boot整合Mybatis +

Spring Cloud系列Eureka原始碼解析之服務端

一、自動裝配   1、根據自動裝配原理(詳見:Spring Boot系列(二):Spring Boot自動裝配原理解析),找到spring-cloud-starter-netflix-eureka-server.jar的spring.factories,檢視spring.factories如下:   2、進

Java多線程編程模式實戰指南Two-phase Termination模式

增加 row throws mgr 額外 finally join table 還需 停止線程是一個目標簡單而實現卻不那麽簡單的任務。首先,Java沒有提供直接的API用於停止線程。此外,停止線程時還有一些額外的細節需要考慮,如待停止的線程處於阻塞(等待鎖)或者等待狀態(等

wifi認證Portal開發系列portal協議

tro spa size http log ron 認證 gin auto 中國移動WLAN業務PORTAL協議規範介紹 wifi認證Portal開發系列(三):portal協議

JavaScript夯實基礎系列this

瀏覽器 系列 中一 對象屬性 轉化 繼續 存儲 www 能夠 ??在JavaScript中,函數的每次調用都會擁有一個執行上下文,通過this關鍵字指向該上下文。函數中的代碼在函數定義時不會執行,只有在函數被調用時才執行。函數調用的方式有四種:作為函數調用、作為方法調用、作

.Net Core 商城微服務項目系列Ocelot網關接入Grafana監控

dap 商城 fig 異常類 uri 部分 中標 timeout doc 使用網關之後我們面臨的一個問題就是監控,我們需要知道網關的實時狀態,比如當前的請求吞吐量、請求耗費的時間、請求峰值甚至需要知道具體哪個服務的哪個方法花費了多少時間。網關作為請求的中轉點是監控品牌的要塞

詳解SVM系列線性可分支援向量機與硬間隔最大化

支援向量機概覽(support vector machines SVM) 支援向量機是一種二類分類模型。它的基本模型是定義在特徵空間上的間隔最大(間隔最大區別於感知機)線性分類器(核函式可以用非線性的分類)。 支援向量機的學習策略是間隔最大化可形式化為一個求解凸二次規劃的問題。 也等