公司自研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 同時也是一個處理器上下文對象