[系列文章目錄和關於我](https://www.cnblogs.com/cuzzz/p/16609728.html) ## 零丶引入 在[Netty源碼學習2——NioEventLoop的執行](https://www.cnblogs.com/cuzzz/p/17641482.html)中,我們學 ...
零丶引入
在Netty源碼學習2——NioEventLoop的執行中,我們學習了NioEventLoop是如何進行事件迴圈以及如何修複NIO 空輪詢的bug的,但是沒有深入瞭解IO事件在netty中是如何被處理的,下麵我們以服務端demo代碼為例子,看下和IO事件處理密切的Channel
如上在編寫netty 服務端的時候,我們一般只需要指定Channel類型,以及實現ChannelHandler在對應方法中編寫業務邏輯代碼即可。
在Netty中,NioEventLoop是事件的調度中心,它控制了Io事件和其他任務的調度,但是io事件的處理是依賴ChannelHandler的,多個ChannelHandler又由ChannelPipline組裝成流水線依次執行
這篇博客我們以此為切入點,看看Channel是如何初始化的,如何和EventLoop關聯起來的,後續看看ChannelPipline是如何組織ChannelHandler的。
一丶Channel概述
Channel是Netty抽象出來的對網路I/O進行讀寫的相關介面,與JDK NIO中的Channel介面類似。Channel的主要功能有網路I/O的讀寫,綁定埠,客戶端發起連接、主動關閉連接、獲取通信雙方網路地址等。
下麵是NioServerSocketChannel,和NioSocketChannel的類圖
-
ChannelOutboundInvoker:定義了bind,connect,disconnect,close等方法。
-
Channel:Netty抽象出來的對網路I/O進行讀寫的相關介面,其中有兩個關鍵的方法
可看出EventLoop和Channel的關係——Channel是註冊到EventLoop中的。
ChannelPipeline和Channel的關係——每一個Channel會分配一個ChannelPipline。
-
AbstractChannel:Channel基本的實現,在其中有幾個重要的屬性:
- unsafe:非jdk中的unsafe,這是netty定義的unsafe,其中定義了bind,connect等方法,在實際和網路打交道的時候會使用到
- DefaultChannelPipeline:ChannelPipeline的預設實現,這就是Channel分配的ChannelPipline
- EventLoop:此Channel註冊到的EventLoop
-
AbstractNioChannel:使用Selector進行IO多路復用的Channel,其中有兩個重要的屬性
從此可與看出,Netty中的Nio Channel 和 jdk中Channel的關係,它們是一對一的
AbstractNioChannel擁有NIO的Channel,具備NIO的註冊、連接等功能。但IO的讀寫交給了其子類。
-
AbstractNioByteChannel&AbstractNioMessageChannel
AbstractNioByteChannel是面向位元組的,通常是客戶端進行使用,AbstractNioMessageChannel面向消息,通過是服務端進行使用。為什麼netty這樣設計昵?
通常情況下,客戶端只需要發送消息,因此直接將消息內容轉換為位元組流進行輸出即可,這時候使用AbstractNioByteChannel更為合適。 服務端在接收到客戶端的消息後,需要對消息進行解碼、反序列化、處理等操作,這時候使用AbstractNioMessageChannel更為合適。因為AbstractNioMessageChannel提供了方便的消息處理功能,可以對收到的位元組流進行解碼、轉換成特定的消息對象,並提供了方便的事件驅動機制,方便開發者對消息進行處理和管理。 例如,在自定義的RPC協議中,服務端需要解碼和反序列化請求消息,調用相應的服務方法,並將響應消息轉換為位元組流輸出。使用AbstractNioMessageChannel可以方便地處理這些消息讀寫操作,使用自定義的解碼器和編碼器可以將位元組流轉換為特定的消息對象,並且在ChannelHandler中實現業務邏輯,通過事件驅動機制方便地管理消息的讀寫和處理。 綜上所述,AbstractNioByteChannel適合用於處理位元組流數據,適合用於客戶端。而AbstractNioMessageChannel適合用於處理消息,適合用於服務端
-
NioSocketChannel
NioSocketChannel是AbstractNioByteChannel的子類,NioSocketChannel封裝了NIO中的SocketChannel,實現了IO的讀寫連接操作。Netty服務的每個連接都會生成一個NioSocketChannel對象。
-
NioServerSocketChannel
NioServerSocektChannel是AbstractNioMessageChannel的子類,一般是服務端進行使用,並且只負責監聽Socket的接入,不關心IO讀寫。
二丶服務端啟動是NioServerSocketChannel是如何實例化並註冊到EventLoop中的
客戶端的NioSocketChannel實例化和註冊流程與服務端類似
如上代碼中,服務端指定了Channel類型,然後調用bind方法,在bind方法中會進行Channel的初始化+註冊到EventLoop中
1.NioServerSocketChannel的實例化
在不指定ChannelFactory的時候,這裡預設是ReflectiveChannelFactory,如同其名稱一樣,ReflectiveChannelFactory會使用反射的方式構建出Channel
隨後會使用SelectorProvider#openServerSocketChannel
創建出一個jdk原生的ServerSocketChannel。
然後調用父類構造器,設置Channel為非阻塞,並調用newUnsafe和newChannelPipeline實例化unsafe和channelPipeline
對於服務端來說這裡newUnsafe產生的是NioMessageUnsafe,ChannelPipeline通常使用的是DefaultChannelPipeline
2.NioServerSocketChannel的初始化
初始化會將我們在ServerBootStrap中設置的參數設置到NioServerSocketChannel中
並向ChannelPipeline添加一個ServerBootstrapAcceptor,ServerBootstrapAcceptor和Netty的reactor模式有關,此類的作用後續進行學習。
3.NioServerSocketChannel的註冊
隨後會使用ServerBootStrap中的EventLoopGroup#register方法進行註冊,這裡的使用的EventLoopGroup是demo中指定的bossGroup
因為在Reactor模式中,bossGroup負責處理ACCEPT事件,單線程使用Selector監聽多路IO的Accept事件,然後將這些套接字交給上面的WorkerGroup,so這裡NioServerSocketChannel註冊到bossGroup中
bossGroup並不會自己進行註冊,而是使用next方法找到自己的小弟——EventLoop,進行註冊
這裡會使用到EventExecutorChooser進行選擇EventLoop,Netty自帶兩種策略
-
如果EventLoopGroup中的EventLoop是2的冪次個,那麼使用PowerOfTwoEventExecutorChooser
它使用取模在眾多EventLoop中選擇一個
-
如果EventLoopGroup中的EventLoop不是2的冪次個,那麼使用GenericEventExecutorChooser,直接對EventLoop個數進行取模
選擇完EventLoop後,會調用EventLoop的註冊方法,最終會使用AbstractUnsafe#register,其中會先判斷執行的線程是不是EventLoop線程,如果不是那麼會將任務提交到EventLoop中執行,源碼如下:
註冊的即將當前Channel註冊到Selector,並且attachment指定為當前Channel,這樣NioEventLoop在進行IO多路復用的的時候,可通過attachment方法拿到當前Channel
註冊結束後會使用ChannelPipeline觸發channelRegistered事件,關於ChannelPipeline下一篇博客中進行學習。
在這一步,還會觸發NioEventLoop線程的啟動,進行事件迴圈,在一個死迴圈中使用Selector監聽這個Channel的IO事件,並處理其他調度任務,非同步任務。(如何啟動NioEventLoop線程的——Netty源碼學習2——NioEventLoop的執行#NioEventLoop的啟動)
三丶ChannelPipeline
上面我們說到一個Channel的實例化會觸發ChannelPipeline的實例化。ChannelPipeline 和 ChannelHandler 也是我們在平時應用開發的過程中打交道最多的組件,通常程式員使用Netty進行開發只需要將自己定義的ChannelHandler加入到ChannelPipeline中。
ChannelPipeline即是ChannelHandler的流水線,ChannelPipeline 可以看作是 ChannelHandler 的容器載體,它是由一組 ChannelHandler 實例組成的,內部通過雙向鏈表將不同的 ChannelHandler 鏈接在一起,如下圖所示。當有 I/O 讀寫事件觸發時,ChannelPipeline 會依次調用 ChannelHandler 列表對 Channel 的數據進行攔截和處理。
1.ChannelHandlerContext
ChannelHandlerContext 用於保存 ChannelHandler 上下文,其包含了 ChannelHandler 生命周期的所有事件,如 connect、bind、read、flush、write、close 等。
2.ChannelInboundHandler 和 ChannelOutboundHandler
在客戶端與服務端通信的過程中,數據從客戶端發向服務端的過程叫出站,反之稱為入站。數據先由一系列 InboundHandler 處理後入站,然後再由相反方向的 OutboundHandler 處理完成後出站。
3.DefaultChannelPipeline
DefaultChannelPipeline是netty中ChannelPipeline的預設實現,內部保存了HeadContext,和TailContext分別作為鏈表的頭和尾
可以看到HeadContext即是ChannelOutboundInvoker(出站處理器)也是ChannelInboundInvoker(出站處理器),這是因為網路數據寫入操作的入口就是由 HeadContext 節點完成的。HeadContext 作為 Pipeline 的頭結點負責讀取數據並開始傳遞 入站事件,當數據處理完成後,數據會反方向經過各個 ChannelOutboundInvoker的處理,最終傳遞到 HeadContext。
而TailContext只實現了ChannelInboundInvoker,它是最後一個ChannelInboundInvoker,用於結束入站事件的傳播。
4.ChannelInboundHandler&ChannelOutboundHandler
二者都是ChannelHandler的子介面,其方法的聲明對於了Netty中對事件的抽象
4.1 ChannelInboundHandler
方法名&事件 | |
---|---|
channelRegistered | 當Channel註冊到它的EventLoop並且能夠處理I/O時調用 |
channelUnregistered | 當Channel從它的EventLoop中註銷並且無法處理任何I/O時調用 |
channelActive | 當Channel處理於活動狀態時被調用 |
channelInactive | 不再是活動狀態且不再連接它的遠程節點時被調用 |
channelReadComplete | 當Channel上的一個讀操作完成時被調 |
channelRead | 當從Channel讀取數據時被調用 |
channelWritabilityChanged | 當Channel的可寫狀態發生改變時被調用 |
userEventTriggered | 當ChannelInboundHandler.fireUserEventTriggered()方法被調用時觸發 |
4.2 ChannelOutBoundHandler
方法名&事件 | |
---|---|
bind | 當請求將Channel綁定到本地地址時被調用 |
connet | 當請求將Channel連接到遠程節點時被調用 |
disconnect | 當請求將Channel從遠程節點斷開時調用 |
close | 當請求關閉Channel時調用 |
deregister | 當請求將Channel從它的EventLoop註銷時調用 |
read | 當請求從Channel中讀取數據時調用 |
flush | 當請求通過Channel將入隊數據沖刷到遠程節點時調用 |
write | 當請求通過Channel將數據寫入遠程節點時被調用 |
四丶總結
此篇初探了Channel,ChannelPipeline,ChannelContext,ChannelHandler之間的關係,深入學習了Netty中的Nio Channel是怎麼和jdk中的Channel組織起來的。
上面我們說到在Netty中,NioEventLoop是事件的調度中心,它控制了Io事件和其他任務的調度,但是io事件的處理是依賴ChannelHandler的,多個ChannelHandler又由ChannelPipline組裝成流水線依次執行
那麼一個網路請求在Netty中是怎麼從NioEventLoop事件迴圈中交由ChannelPipline進行事件傳播與處理的昵?這個下篇中進行學習和總結。