Netty源碼學習(2)--服務器啟動

公司自研rpc框架,底層網絡通信框架為netty;作為it小白,有必要學習rpc框架及對應的系統底層網絡通信框架。前一篇文章初步了解nio內容,下面開始逐步學習netty源碼內容。


內容參考http://www.lxweimin.com/p/c5068caab217?中的部分內容。

使用版本為:

Netty 服務端創建的時序圖,如下(摘自《Netty權威指南(第二版)》)

舉例:服務端啟動代碼:

開啟一個服務端,端口綁定在8888,使用nio模式,下面講下每一個步驟的處理細節

EventLoopGroup :

? ? ? ? 是一個死循環,不停地檢測IO事件,處理IO事件,執行任務,后面詳細描述;初始化用于Acceptor的主"線程池"以及用于I/O工作的從"線程池";

ServerBootstrap :?

? ? ? ? 初始化ServerBootstrap實例, 此實例是netty服務端應用開發的入口是服務端的一個啟動輔助類,通過給他設置一系列參數來綁定端口啟動服務;

.channel(NioServerSocketChannel.class) :

? ? ? ?指定通道channel的類型,由于是服務端,故而是NioServerSocketChannel;表示服務端啟動的是nio相關的channel,channel在netty里面是一大核心概念,可以理解為一條channel就是一個連接或者一個服務端bind動作;

b.childHandler(new NettyServerFilter())?表表示一條新的連接進來之后,該怎么處理,NettyServerFilter代碼如圖中所示:

ChannelFuturef =b.bind(port).sync()??這里就是真正的啟動過程了,綁定6789端口,等待服務器啟動完畢,才會進入下行代碼。

詳細描述:

ServerBootstrap的各個參數的配置不再描述;直接跳入到bind()方法:

逐步step:

通過端口號創建一個?InetSocketAddress,然后繼續step:

其中validate()方法用于驗證服務啟動需要的必要參數是否合格

如果參數合格,則調用dobind()

去掉細枝末節,讓我們專注于核心方法,其實就兩大核心一個是?initAndRegister(),以及doBind0();

initAndRegister() 聯系到nio里面輪詢器的注冊,可能是把某個東西初始化好了之后注冊到selector上面去,最后bind,像是在本地綁定端口號,帶著這些猜測,我們深入下去

initAndRegister()?

核心代碼如圖中紅框部分;

1. new 一個channel,

2. init這個channel,即調用init(channel)初始化通道信息

3. 將這個channel register到某個對象。

后面逐一分析:

1. new 一個channel??

final Channel channel = channelFactory().newChannel();

調用channelFactory生成通道channel實例,通過serverbootstrap的channel方法來指定通道類型,其實是調用基類AbstractBoostrap的channel方法,其內部其實是實例化了一個產生指定channel類型的channelFactory。

所以,initAndRegister中的channelFactory.newChannel()方法就是生成了一個NioServerSocketChannel的實例

channel的定義,netty官方對channel的描述如下

A nexus to a network socket or a component which is capable of I/O operations such as read, write, connect, and bind

這里的channel,由于是在服務啟動的時候創建,我們可以和普通Socket編程中的ServerSocket對應上,表示服務端綁定的時候經過的一條流水線。這條channel是通過一個?channelFactory?new出來的,channelFactory?的接口很簡單:

就一個方法,我們查看channelFactory被賦值的地方

在這里被賦值,我們層層回溯,查看該函數被調用的地方,發現最終是在這個函數channel()中,ChannelFactory被new出

demo程序調用channel(channelClass)方法的時候,傳入參數

將channelClass作為BootstrapChannelFactory的構造函數創建一個BootstrapChannelFactory。

最終,在initAndRegister() 方法的開始階段final Channel channel = channelFactory().newChannel();初始化創建channel是調用的BootstrapChannelFactory.newChannel()方法,即AbstractBootstrap類中的內部類BootstrapChannelFactory中的newChannel();

看到clazz.newInstance() 是通過反射的方式來創建一個對象,而這個clazz就是我們在ServerBootstrap中傳入的NioServerSocketChannel.class

創建channel相當于調用默認構造函數new出一個?NioServerSocketChannel對象

下面分析??NioServerSocketChannel的默認構造函數:

無參構造方法中有兩個關鍵點:

1、使用默認的多路復用器輔助類 DEFAULT_SELECTOR_PROVIDER

2、通過newSocket創建ServerSocketChannel

通過SelectorProvider.openServerSocketChannel()創建一條server端channel(后面會在AbstractNioChannel.java的構造函數中保存為成員變量),然后進入到以下方法,即:將newSocket生成的ServerSocketChannel對象繼續傳遞給本類中的NioServerSocketChannel(ServerSocketChannel channel)構造方法

因為是服務端新生成的channel,第一個參數指定為null,表示沒有父channel,

第二個參數指定為ServerSocketChannel,第三個參數指定ServerSocketChannel關心的事件類型為SelectionKey.OP_ACCEPT。

第一行代碼就跑到父類里面去了;?

第二行new出來一個?NioServerSocketChannelConfig,其頂層接口為?ChannelConfig,.

super(null, channel, SelectionKey.OP_ACCEPT);

追蹤到?NioServerSocketChannel?的父類 : AbstractNioMessageChannel.java

繼續追蹤到父類:AbstractNioChannel.java

將前面?provider.openServerSocketChannel();?創建出來的?ServerSocketChannel保存到成員變量ch

然后調用ch.configureBlocking(false);設置該channel為非阻塞模式

這里的?readInterestOp?即前面層層傳入的?SelectionKey.OP_ACCEPT,接下來重點分析?super(parent);(這里的parent其實是null,由前面寫死傳入);

?在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.java; 構造函數入切圖:

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

1、給channel生成一個新的id

2、通過newUnsafe初始化channel的unsafe屬性

3、pipeline =new DefaultChannelPipeline(this) 初始化channel的pipeline屬性

此處:new出來三大組件,賦值到成員變量,分別為

1? this.parent = parent; 此時傳入的是null

2? unsafe = newUnsafe();

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

AbstractNioMessageChannel類中有newUnsafe()的實現

此方法返回一個NioMessageUnsafe實例對象,而NioMessageUnsafe是AbstractNioMessageChannel的內部類

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

OP_ACCEPT都已經注冊上了,當接收到新用戶連接時就會觸發unsafe.read()方法。read()會不斷調用doReadMessages(),將產生的readBuf逐一發送給Pipeline.fireChannelRead()去處理。

unsafe內容后續學習;

3? pipeline =new DefaultChannelPipeline(this);

初始化了HeadContext及TailContext對象。

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

通過上面的代碼可以得出,pipeline就是一個雙向鏈表。

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

config =newNioServerSocketChannelConfig(this, javaChannel().socket());

ioServerSocketChannelConfig是NioServerSocketChannel的內部類

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

至此NioServerSocketChannel對象應該創建完成。

總結:

用戶調用方法?Bootstrap.bind(port)?第一步就是通過反射的方式new一個NioServerSocketChannel對象,并且在new的過程中創建了一系列的核心組件,進一步研究:

1、NioServerSocketChannel對象內部綁定了Java NIO創建的ServerSocketChannel對象;

2、Netty中,每個channel都有一個unsafe對象,此對象封裝了Java NIO底層channel的操作細節;

3、Netty中,每個channel都有一個pipeline對象,此對象就是一個雙向鏈表;

NioServerSocketChannel的類繼承結構圖:

2.init這個channel

init(channel);

// 設置引導類配置的option

finalMap, Object> options = options0();

option:?設置通道的選項參數, 對于服務端而言就是ServerSocketChannel, 客戶端而言就是SocketChannel;

// 設置引導類配置的attr,attr:?設置通道的屬性;

attrs = attrs();

1. 上圖這里先調用options0()以及attrs0(),然后將得到的options和attrs注入到channelConfig或者channel中

2. 上圖代碼第一行,?獲取當前通道的pipeline,然后為 NioServerSocketChanne l綁定的 pipeline 添加 Handler;

3.?將用于服務端注冊的 Handler ServerBootstrapAcceptor 添加到 ChannelPipeline 中。ServerBootstrapAcceptor 為一個接入器,專門接受新請求,把新的請求扔給某個事件循環器。對應到新進來連接對應的channel。

p.addLast()向serverChannel的流水線處理器中加入了一個?ServerBootstrapAcceptor,從名字上就可以看出來,這是一個接入器,專門接受新請求,把新的請求扔給某個事件循環器

3.將這個channel register到某個對象

ChannelFuture regFuture = group().register(channel);

調用到MultithreadEventLoopGroup的register;

然后調用到SingleThreadEventLoop中的register方法:

關鍵是:channel().unsafe().register(this, promise);

register跳轉到AbstractUnsafe.java中的register

先將EventLoop事件循環器綁定到該NioServerSocketChannel上,然后調用?register0()

register0()方法定義了注冊到EventLoop的整體框架,整個流程如下:

(1).注冊的具體細節由doRegister()方法完成,子類中實現。

(2).注冊后將處理業務邏輯的用戶Handler添加到ChannelPipeline。fireChannelRegistered方法中實現。

invokeHandlerAddedIfNeeded()

(3).異步結果設置為成功,觸發Channel的Registered事件。

(4).對于服務端接受的客戶端連接,如果首次注冊,觸發Channel的Active事件,如果已設置autoRead,則調用read()開始讀取數據。

上面的條件isActive()?方法,?NioServerSocketChannel接受的Channel此時已被激活

在NioServerSocketChannel中重寫此方法:

最終調用到jdk中,bound初始定義為false;oldImpl 標識為false

最終isBound返回false;

所以?isActive()?返回false;

如果isActive返回true(例如使用舊socket導致oldImpl為true)此時跳入到下列方法中:

跳轉到AbstractChannelHandlerContext類中的 下列方法:

在 Outbound 事件(例如 Connect 事件)的傳輸過程中時, 我們也有類似的操作:

首先調用 findContextInbound, 從 Pipeline 的雙向鏈表中中找到第一個屬性 inbound 為真的 Context, 然后返回

調用這個 Context 的 invokeChannelActive

這個方法和 Outbound 的對應方法(例如 invokeConnect) 如出一轍. 同 Outbound 一樣, 如果用戶沒有重寫 channelActive 方法, 那么會調用 ChannelInboundHandlerAdapter 的 channelActive 方法,channelHandler相關內容后續學習。

isAutoRead方法默認返回true,于是進入到以下方法。


最終AbstractChannelHandlerContext中的read方法

findContextOutbound() 顧名思義, 它的作用是以當前 Context 為起點, 向 Pipeline 中的 Context 雙向鏈表的前端尋找第一個?outbound屬性為真的 Context(即關聯著 ChannelOutboundHandler 的 Context), 然后返回.

詳細分析后面學習。

總結,netty啟動一個服務所經過的流程

1. 設置啟動類參數,最重要的就是設置channel

2.? 創建server對應的channel,創建各大組件,包括ChannelConfig,ChannelId,ChannelPipeline,ChannelHandler,Unsafe等

3.? 初始化server對應的channel,設置一些attr,option,以及設置子channel的attr,option,給server的channel添加新channel接入器,并出發addHandler,register等事件:attr:?設置通道的屬性;

4.? 調用到jdk底層做端口綁定,并觸發active事件,active觸發的時候,真正做服務費端口綁定.

5.?在進行服務端開發時,必須通過ServerBootstrap引導類的channel方法來指定channel類型, channel方法的調用其實就是實例化了一個用于生成此channel類型對象的工廠對象。 并且在bind調用后,會調用此工廠對象來生成一個新channel。


Pipeline初始化過程分析

inbound:?本質上就是執行I/O線程將從外部read到的數據 傳遞給 業務線程的一個過程。

outbound: 本質上就是業務線程 將數據 傳遞給I/O線程, 直至發送給外部的一個過程。

過程如圖:


channel中的pipeline其實就是DefaultChannelPipeline的實例

class DefaultChannelPipelineimplements ChannelPipeline

DefaultChannelPipelineimplements 實現了ChannelPipeline接口;

DefaultChannelPipelineimplements構造函數如圖所示;初始化一個pipeline,此時定義tail head;

首先綁定channel對象,然后初始化頭、尾上下文,然后頭尾相互連接,形成雙向鏈表。

head是HeadContext的實例,tail是TailContext的實例,HeadContext與TailContext都是DefaultChannelPipeline的內部類,它們的類繼承結構圖如下:

HeadContext類繼承結構圖

TailContext類繼承結構圖

從類繼承圖我們可以看出:

1、HeadContext與TailContext都是通道的handler(中文一般叫做處理器)

2、HeadContext可以用于outbound過程的handler

3、TailContext只可以用于inbound過程的handler

4、HeadContext 與 TailContext 同時也是一個處理器上下文對象

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容