公司自研rpc框架借跪,底層網(wǎng)絡(luò)通信框架為netty溶握;作為it小白,有必要學(xué)習rpc框架及對應(yīng)的系統(tǒng)底層網(wǎng)絡(luò)通信框架砰左。前一篇文章初步了解nio內(nèi)容匿醒,下面開始逐步學(xué)習netty源碼內(nèi)容。
內(nèi)容參考http://www.reibang.com/p/c5068caab217?中的部分內(nèi)容缠导。
使用版本為:
Netty 服務(wù)端創(chuàng)建的時序圖廉羔,如下(摘自《Netty權(quán)威指南(第二版)》)
舉例:服務(wù)端啟動代碼:
開啟一個服務(wù)端,端口綁定在8888僻造,使用nio模式憋他,下面講下每一個步驟的處理細節(jié)
EventLoopGroup :
? ? ? ? 是一個死循環(huán),不停地檢測IO事件髓削,處理IO事件竹挡,執(zhí)行任務(wù),后面詳細描述立膛;初始化用于Acceptor的主"線程池"以及用于I/O工作的從"線程池"此迅;
ServerBootstrap :?
? ? ? ? 初始化ServerBootstrap實例, 此實例是netty服務(wù)端應(yīng)用開發(fā)的入口是服務(wù)端的一個啟動輔助類旧巾,通過給他設(shè)置一系列參數(shù)來綁定端口啟動服務(wù);
.channel(NioServerSocketChannel.class) :
? ? ? ?指定通道channel的類型耸序,由于是服務(wù)端,故而是NioServerSocketChannel鲁猩;表示服務(wù)端啟動的是nio相關(guān)的channel坎怪,channel在netty里面是一大核心概念,可以理解為一條channel就是一個連接或者一個服務(wù)端bind動作;
b.childHandler(new NettyServerFilter())?表表示一條新的連接進來之后廓握,該怎么處理搅窿,NettyServerFilter代碼如圖中所示:
ChannelFuturef =b.bind(port).sync()??這里就是真正的啟動過程了,綁定6789端口隙券,等待服務(wù)器啟動完畢男应,才會進入下行代碼。
詳細描述:
ServerBootstrap的各個參數(shù)的配置不再描述娱仔;直接跳入到bind()方法:
逐步step:
通過端口號創(chuàng)建一個?InetSocketAddress沐飘,然后繼續(xù)step:
其中validate()方法用于驗證服務(wù)啟動需要的必要參數(shù)是否合格
如果參數(shù)合格,則調(diào)用dobind()
去掉細枝末節(jié),讓我們專注于核心方法耐朴,其實就兩大核心一個是?initAndRegister()借卧,以及doBind0();
initAndRegister() 聯(lián)系到nio里面輪詢器的注冊,可能是把某個東西初始化好了之后注冊到selector上面去筛峭,最后bind铐刘,像是在本地綁定端口號,帶著這些猜測影晓,我們深入下去
initAndRegister()?
核心代碼如圖中紅框部分镰吵;
1. new 一個channel,
2. init這個channel挂签,即調(diào)用init(channel)初始化通道信息
3. 將這個channel register到某個對象捡遍。
后面逐一分析:
1. new 一個channel??
final Channel channel = channelFactory().newChannel();
調(diào)用channelFactory生成通道channel實例,通過serverbootstrap的channel方法來指定通道類型竹握,其實是調(diào)用基類AbstractBoostrap的channel方法画株,其內(nèi)部其實是實例化了一個產(chǎn)生指定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芹关,由于是在服務(wù)啟動的時候創(chuàng)建续挟,我們可以和普通Socket編程中的ServerSocket對應(yīng)上,表示服務(wù)端綁定的時候經(jīng)過的一條流水線侥衬。這條channel是通過一個?channelFactory?new出來的诗祸,channelFactory?的接口很簡單:
就一個方法,我們查看channelFactory被賦值的地方
在這里被賦值轴总,我們層層回溯直颅,查看該函數(shù)被調(diào)用的地方,發(fā)現(xiàn)最終是在這個函數(shù)channel()中怀樟,ChannelFactory被new出
demo程序調(diào)用channel(channelClass)方法的時候功偿,傳入?yún)?shù)
將channelClass作為BootstrapChannelFactory的構(gòu)造函數(shù)創(chuàng)建一個BootstrapChannelFactory。
最終往堡,在initAndRegister() 方法的開始階段final Channel channel = channelFactory().newChannel()械荷;初始化創(chuàng)建channel是調(diào)用的BootstrapChannelFactory.newChannel()方法,即AbstractBootstrap類中的內(nèi)部類BootstrapChannelFactory中的newChannel();
看到clazz.newInstance() 是通過反射的方式來創(chuàng)建一個對象虑灰,而這個clazz就是我們在ServerBootstrap中傳入的NioServerSocketChannel.class
創(chuàng)建channel相當于調(diào)用默認構(gòu)造函數(shù)new出一個?NioServerSocketChannel對象
下面分析??NioServerSocketChannel的默認構(gòu)造函數(shù):
無參構(gòu)造方法中有兩個關(guān)鍵點:
1吨瞎、使用默認的多路復(fù)用器輔助類 DEFAULT_SELECTOR_PROVIDER
2、通過newSocket創(chuàng)建ServerSocketChannel
通過SelectorProvider.openServerSocketChannel()創(chuàng)建一條server端channel(后面會在AbstractNioChannel.java的構(gòu)造函數(shù)中保存為成員變量)穆咐,然后進入到以下方法颤诀,即:將newSocket生成的ServerSocketChannel對象繼續(xù)傳遞給本類中的NioServerSocketChannel(ServerSocketChannel channel)構(gòu)造方法
因為是服務(wù)端新生成的channel,第一個參數(shù)指定為null,表示沒有父channel着绊,
第二個參數(shù)指定為ServerSocketChannel,第三個參數(shù)指定ServerSocketChannel關(guān)心的事件類型為SelectionKey.OP_ACCEPT熟尉。
第一行代碼就跑到父類里面去了;?
第二行new出來一個?NioServerSocketChannelConfig归露,其頂層接口為?ChannelConfig,.
super(null, channel, SelectionKey.OP_ACCEPT);
追蹤到?NioServerSocketChannel?的父類 : AbstractNioMessageChannel.java
繼續(xù)追蹤到父類:AbstractNioChannel.java
將前面?provider.openServerSocketChannel();?創(chuàng)建出來的?ServerSocketChannel保存到成員變量ch
然后調(diào)用ch.configureBlocking(false);設(shè)置該channel為非阻塞模式
這里的?readInterestOp?即前面層層傳入的?SelectionKey.OP_ACCEPT斤儿,接下來重點分析?super(parent);(這里的parent其實是null剧包,由前面寫死傳入);
?在AbstractNioChannel中做了下面幾件事:
1往果、繼續(xù)調(diào)用父類AbstractChannel(Channel parent)構(gòu)造方法疆液;
2、通過this.ch = ch 保存ServerSocketChannel陕贮, 因為NioServerSocketChannel是Netty封裝的對象堕油,而ServerSocketChannel是有前面默認selector_provider生成的,是java nio的肮之, 其實“this.ch = ch”可以被認為是綁定java nio服務(wù)端通道至netty對象中掉缺;
3、設(shè)置ServerSocketChannel關(guān)心的事件類型戈擒;
4眶明、設(shè)置ServerSocketChannel為非阻塞的(熟悉Java NIO的都知道如果不設(shè)置為false,啟動多路復(fù)用器會報異
父類為AbstractChannel.java; 構(gòu)造函數(shù)入切圖:
此構(gòu)造方法中筐高,主要做了三件事:
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()的實現(xiàn)
此方法返回一個NioMessageUnsafe實例對象幌缝,而NioMessageUnsafe是AbstractNioMessageChannel的內(nèi)部類
NioMessageUnsafe 只覆蓋了 父類AbstractNioUnsafe中的read方法,通過NioMessageUnsafe 及其父類的代碼便可以知道诫欠, 其實unsafe對象是真正的負責底層channel的連接/讀/寫等操作的涵卵,unsafe就好比一個底層channel操作的代理對象。
OP_ACCEPT都已經(jīng)注冊上了荒叼,當接收到新用戶連接時就會觸發(fā)unsafe.read()方法轿偎。read()會不斷調(diào)用doReadMessages(),將產(chǎn)生的readBuf逐一發(fā)送給Pipeline.fireChannelRead()去處理被廓。
unsafe內(nèi)容后續(xù)學(xué)習坏晦;
3? pipeline =new DefaultChannelPipeline(this);
初始化了HeadContext及TailContext對象。
head及tail初始化完成后,它們會相互連接昆婿。
通過上面的代碼可以得出球碉,pipeline就是一個雙向鏈表。
回到NioServerSocketChannel的構(gòu)造方法 NioServerSocketChannel(ServerSocketChannel channel)
config =newNioServerSocketChannelConfig(this, javaChannel().socket());
ioServerSocketChannelConfig是NioServerSocketChannel的內(nèi)部類
而NioServerSocketChannelConfig 又是繼承自DefaultServerSocketChannelConfig仓蛆,通過代碼分析睁冬,此config對象就是就會對底層ServerSocket一些配置設(shè)置行為的封裝。
至此NioServerSocketChannel對象應(yīng)該創(chuàng)建完成看疙。
總結(jié):
用戶調(diào)用方法?Bootstrap.bind(port)?第一步就是通過反射的方式new一個NioServerSocketChannel對象豆拨,并且在new的過程中創(chuàng)建了一系列的核心組件,進一步研究:
1能庆、NioServerSocketChannel對象內(nèi)部綁定了Java NIO創(chuàng)建的ServerSocketChannel對象施禾;
2、Netty中搁胆,每個channel都有一個unsafe對象弥搞,此對象封裝了Java NIO底層channel的操作細節(jié);
3渠旁、Netty中拓巧,每個channel都有一個pipeline對象,此對象就是一個雙向鏈表一死;
NioServerSocketChannel的類繼承結(jié)構(gòu)圖:
2.init這個channel
init(channel);
// 設(shè)置引導(dǎo)類配置的option
finalMap, Object> options = options0();
option:?設(shè)置通道的選項參數(shù)肛度, 對于服務(wù)端而言就是ServerSocketChannel, 客戶端而言就是SocketChannel投慈;
// 設(shè)置引導(dǎo)類配置的attr承耿,attr:?設(shè)置通道的屬性;
attrs = attrs();
1. 上圖這里先調(diào)用options0()以及attrs0()伪煤,然后將得到的options和attrs注入到channelConfig或者channel中
2. 上圖代碼第一行加袋,?獲取當前通道的pipeline,然后為 NioServerSocketChanne l綁定的 pipeline 添加 Handler抱既;
3.?將用于服務(wù)端注冊的 Handler ServerBootstrapAcceptor 添加到 ChannelPipeline 中职烧。ServerBootstrapAcceptor 為一個接入器,專門接受新請求防泵,把新的請求扔給某個事件循環(huán)器蚀之。對應(yīng)到新進來連接對應(yīng)的channel。
p.addLast()向serverChannel的流水線處理器中加入了一個?ServerBootstrapAcceptor捷泞,從名字上就可以看出來足删,這是一個接入器,專門接受新請求锁右,把新的請求扔給某個事件循環(huán)器
3.將這個channel register到某個對象
ChannelFuture regFuture = group().register(channel);
調(diào)用到MultithreadEventLoopGroup的register失受;
然后調(diào)用到SingleThreadEventLoop中的register方法:
關(guān)鍵是:channel().unsafe().register(this, promise);
register跳轉(zhuǎn)到AbstractUnsafe.java中的register
先將EventLoop事件循環(huán)器綁定到該NioServerSocketChannel上讶泰,然后調(diào)用?register0()
register0()方法定義了注冊到EventLoop的整體框架,整個流程如下:
(1).注冊的具體細節(jié)由doRegister()方法完成拂到,子類中實現(xiàn)痪署。
(2).注冊后將處理業(yè)務(wù)邏輯的用戶Handler添加到ChannelPipeline。fireChannelRegistered方法中實現(xiàn)兄旬。
invokeHandlerAddedIfNeeded()
(3).異步結(jié)果設(shè)置為成功狼犯,觸發(fā)Channel的Registered事件。
(4).對于服務(wù)端接受的客戶端連接辖试,如果首次注冊辜王,觸發(fā)Channel的Active事件劈狐,如果已設(shè)置autoRead罐孝,則調(diào)用read()開始讀取數(shù)據(jù)。
上面的條件isActive()?方法肥缔,?NioServerSocketChannel接受的Channel此時已被激活
在NioServerSocketChannel中重寫此方法:
最終調(diào)用到j(luò)dk中莲兢,bound初始定義為false;oldImpl 標識為false
最終isBound返回false续膳;
所以?isActive()?返回false改艇;
如果isActive返回true(例如使用舊socket導(dǎo)致oldImpl為true)此時跳入到下列方法中:
跳轉(zhuǎn)到AbstractChannelHandlerContext類中的 下列方法:
在 Outbound 事件(例如 Connect 事件)的傳輸過程中時, 我們也有類似的操作:
首先調(diào)用 findContextInbound, 從 Pipeline 的雙向鏈表中中找到第一個屬性 inbound 為真的 Context, 然后返回
調(diào)用這個 Context 的 invokeChannelActive
這個方法和 Outbound 的對應(yīng)方法(例如 invokeConnect) 如出一轍. 同 Outbound 一樣, 如果用戶沒有重寫 channelActive 方法, 那么會調(diào)用 ChannelInboundHandlerAdapter 的 channelActive 方法,channelHandler相關(guān)內(nèi)容后續(xù)學(xué)習坟岔。
isAutoRead方法默認返回true谒兄,于是進入到以下方法。
最終AbstractChannelHandlerContext中的read方法
findContextOutbound() 顧名思義, 它的作用是以當前 Context 為起點, 向 Pipeline 中的 Context 雙向鏈表的前端尋找第一個?outbound屬性為真的 Context(即關(guān)聯(lián)著 ChannelOutboundHandler 的 Context), 然后返回.
詳細分析后面學(xué)習社付。
總結(jié)承疲,netty啟動一個服務(wù)所經(jīng)過的流程
1. 設(shè)置啟動類參數(shù),最重要的就是設(shè)置channel
2.? 創(chuàng)建server對應(yīng)的channel鸥咖,創(chuàng)建各大組件燕鸽,包括ChannelConfig,ChannelId,ChannelPipeline,ChannelHandler,Unsafe等
3.? 初始化server對應(yīng)的channel,設(shè)置一些attr啼辣,option啊研,以及設(shè)置子channel的attr,option鸥拧,給server的channel添加新channel接入器党远,并出發(fā)addHandler,register等事件:attr:?設(shè)置通道的屬性;
4.? 調(diào)用到j(luò)dk底層做端口綁定富弦,并觸發(fā)active事件麸锉,active觸發(fā)的時候,真正做服務(wù)費端口綁定.
5.?在進行服務(wù)端開發(fā)時舆声,必須通過ServerBootstrap引導(dǎo)類的channel方法來指定channel類型花沉, channel方法的調(diào)用其實就是實例化了一個用于生成此channel類型對象的工廠對象柳爽。 并且在bind調(diào)用后,會調(diào)用此工廠對象來生成一個新channel碱屁。
Pipeline初始化過程分析
inbound:?本質(zhì)上就是執(zhí)行I/O線程將從外部read到的數(shù)據(jù) 傳遞給 業(yè)務(wù)線程的一個過程磷脯。
outbound: 本質(zhì)上就是業(yè)務(wù)線程 將數(shù)據(jù) 傳遞給I/O線程, 直至發(fā)送給外部的一個過程娩脾。
過程如圖:
channel中的pipeline其實就是DefaultChannelPipeline的實例
class DefaultChannelPipelineimplements ChannelPipeline
DefaultChannelPipelineimplements 實現(xiàn)了ChannelPipeline接口赵誓;
DefaultChannelPipelineimplements構(gòu)造函數(shù)如圖所示;初始化一個pipeline柿赊,此時定義tail head俩功;
首先綁定channel對象,然后初始化頭碰声、尾上下文诡蜓,然后頭尾相互連接,形成雙向鏈表胰挑。
head是HeadContext的實例蔓罚,tail是TailContext的實例,HeadContext與TailContext都是DefaultChannelPipeline的內(nèi)部類瞻颂,它們的類繼承結(jié)構(gòu)圖如下:
HeadContext類繼承結(jié)構(gòu)圖
TailContext類繼承結(jié)構(gòu)圖
從類繼承圖我們可以看出:
1豺谈、HeadContext與TailContext都是通道的handler(中文一般叫做處理器)
2、HeadContext可以用于outbound過程的handler
3贡这、TailContext只可以用于inbound過程的handler
4茬末、HeadContext 與 TailContext 同時也是一個處理器上下文對象