在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是Netty抽象出來的對網路I/O進行讀寫的相關介面,與JDK NIO中的Channel介面類似。Channel的主要功能有網路I/O的讀寫,繫結埠,使用者端發起連線、主動關閉連線、獲取通訊雙方網路地址等。
下面是NioServerSocketChannel,和NioSocketChannel的類圖
隨後會使用SelectorProvider#openServerSocketChannel
建立出一個jdk原生的ServerSocketChannel。
然後呼叫父類別構造器,設定Channel為非阻塞,並呼叫newUnsafe和newChannelPipeline範例化unsafe和channelPipeline
對於伺服器端來說這裡newUnsafe產生的是NioMessageUnsafe,ChannelPipeline通常使用的是DefaultChannelPipeline
初始化會將我們在ServerBootStrap中設定的引數設定到NioServerSocketChannel中
並向ChannelPipeline新增一個ServerBootstrapAcceptor,ServerBootstrapAcceptor和Netty的reactor模式有關,此類的作用後續進行學習。
隨後會使用ServerBootStrap中的EventLoopGroup#register方法進行註冊,這裡的使用的EventLoopGroup是demo中指定的bossGroup
註冊的即將當前Channel註冊到Selector,並且attachment指定為當前Channel,這樣NioEventLoop在進行IO多路複用的的時候,可通過attachment方法拿到當前Channel
註冊結束後會使用ChannelPipeline觸發channelRegistered事件,關於ChannelPipeline下一篇部落格中進行學習。
在這一步,還會觸發NioEventLoop執行緒的啟動,進行事件迴圈,在一個死迴圈中使用Selector監聽這個Channel的IO事件,並處理其他排程任務,非同步任務。(如何啟動NioEventLoop執行緒的——Netty原始碼學習2——NioEventLoop的執行#NioEventLoop的啟動)
上面我們說到一個Channel的範例化會觸發ChannelPipeline的範例化。ChannelPipeline 和 ChannelHandler 也是我們在平時應用開發的過程中打交道最多的元件,通常程式設計師使用Netty進行開發只需要將自己定義的ChannelHandler加入到ChannelPipeline中。
ChannelPipeline即是ChannelHandler的流水線,ChannelPipeline 可以看作是 ChannelHandler 的容器載體,它是由一組 ChannelHandler 範例組成的,內部通過雙向連結串列將不同的 ChannelHandler 連結在一起,如下圖所示。當有 I/O 讀寫事件觸發時,ChannelPipeline 會依次呼叫 ChannelHandler 列表對 Channel 的資料進行攔截和處理。
ChannelHandlerContext 用於儲存 ChannelHandler 上下文,其包含了 ChannelHandler 生命週期的所有事件,如 connect、bind、read、flush、write、close 等。
在使用者端與伺服器端通訊的過程中,資料從使用者端發向伺服器端的過程叫出站,反之稱為入站。資料先由一系列 InboundHandler 處理後入站,然後再由相反方向的 OutboundHandler 處理完成後出站。
DefaultChannelPipeline是netty中ChannelPipeline的預設實現,內部儲存了HeadContext,和TailContext分別作為連結串列的頭和尾
可以看到HeadContext即是ChannelOutboundInvoker(出站處理器)也是ChannelInboundInvoker(出站處理器),這是因為網路資料寫入操作的入口就是由 HeadContext 節點完成的。HeadContext 作為 Pipeline 的頭結點負責讀取資料並開始傳遞 入站事件,當資料處理完成後,資料會反方向經過各個 ChannelOutboundInvoker的處理,最終傳遞到 HeadContext。
而TailContext只實現了ChannelInboundInvoker,它是最後一個ChannelInboundInvoker,用於結束入站事件的傳播。
二者都是ChannelHandler的子介面,其方法的宣告對於了Netty中對事件的抽象
方法名&事件 | |
---|---|
channelRegistered | 當Channel註冊到它的EventLoop並且能夠處理I/O時呼叫 |
channelUnregistered | 當Channel從它的EventLoop中登出並且無法處理任何I/O時呼叫 |
channelActive | 當Channel處理於活動狀態時被呼叫 |
channelInactive | 不再是活動狀態且不再連線它的遠端節點時被呼叫 |
channelReadComplete | 當Channel上的一個讀操作完成時被調 |
channelRead | 當從Channel讀取資料時被呼叫 |
channelWritabilityChanged | 當Channel的可寫狀態發生改變時被呼叫 |
userEventTriggered | 當ChannelInboundHandler.fireUserEventTriggered()方法被呼叫時觸發 |
方法名&事件 | |
---|---|
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進行事件傳播與處理的暱?這個下篇中進行學習和總結。