Netty進階:手把手教你如何編寫一個NIO服務(wù)端

Netty是一款非常優(yōu)秀的網(wǎng)絡(luò)編程框架,是對NIO的二次封裝,本文將重點剖析Netty服務(wù)端的啟動流程,深入底層了解如何使用NIO編程服務(wù)端。

本文是筆者基于問題的啟發(fā)式源碼閱讀技巧的展示,建議帶著如下問題開始本文的閱讀:

  • ServerBootstrap 的 option 與 childOption 分別有什么作用

  • 服務(wù)端IO通道如何綁定事件鏈。

  • ServerBootstrap 的 handler 方法與 childHandler 方法的區(qū)別又是什么?

  • childHandler中的方法在服務(wù)端bind方法時會被調(diào)用嗎?

1、Netty服務(wù)端啟動示例


基于Netty的使用示例如下:

圖片

代碼@1:創(chuàng)建主從多Reactor線程模型的Boss線程組,通常只需要設(shè)置一個線程,用于監(jiān)聽客戶端的連接請求(OP_ACCEPT)。

代碼@2:創(chuàng)建主從多Reactor線程模型的Work線程組,即IO線程組,默認為CPU核數(shù)的兩倍。

代碼@3:創(chuàng)建Netty服務(wù)端啟動工具類ServerBootstrap。

代碼@4:調(diào)用group方法設(shè)置主從線程組。

代碼@5:設(shè)置通道的類型,服務(wù)端NIO通道類型 NioServerSocketChannel。

代碼@6:通過option方法為通道服務(wù)端通道選項。

代碼@7:通過chiildOption方法為IO通道設(shè)置選項。

代碼@8:通過ChannelInitializer添加自定義的ChannelHandler,通常包括編碼解碼器、業(yè)務(wù)Handler。

代碼@9:調(diào)用管道的addLast添加自定義編碼解碼器。

代碼@10:調(diào)用bind方法綁定到服務(wù)端指定接口,綁定完成后則在指點端口上監(jiān)聽客戶端的連接。

服務(wù)端的核心流程入口為bind方法,接下來我們將詳細分析其實現(xiàn)原理,繼續(xù)體會NIO編程技巧。

2、Netty服務(wù)端啟動流程


通過跟蹤其bind方法,最終將進入到AbstractBootstrap的doBind方法。

圖片

其關(guān)鍵實現(xiàn)點:
代碼@1:通過調(diào)用initAndRegister方法完成底層網(wǎng)絡(luò)初始化與通道注冊工作。
代碼@2:如果初始化與注冊工作已完成,則直接調(diào)用doBind0方法完成綁定操作。

代碼@3:如果初始化與注冊工作未完成,則通過regFuture(注冊憑證)中添加監(jiān)聽器,等注冊完成后再執(zhí)行doBind0方法。

技巧提示:基于Future異步編程,在主線程中通過調(diào)用future.isDone方法判斷異步方法是否已完成,如果未完成,通過在該憑證上添加監(jiān)聽器(事件回調(diào)),操作完成后執(zhí)行回調(diào)邏輯。

從上面的方法來看服務(wù)端的綁定流程包含初始化與綁定兩個子流程,接下來將分別深入探討。

2.1 通道初始化

基于NIO編程,需要先創(chuàng)建通道,然后將其注冊到事件選擇器,這個過程由 AbstractBootstrap 的 initAndRegister 方法實現(xiàn)。

圖片

實現(xiàn)的關(guān)鍵點如下:
代碼@1:創(chuàng)建NIO服務(wù)端通道實現(xiàn)類NioServerSocketChannel的實例。

代碼@2:調(diào)用 init 方法初始化通道。

代碼@3:將通道注冊到事件輪詢器EventLoopGroup

代碼@4::如果通道已注冊,但發(fā)生了錯誤則調(diào)用通道close方法回收相關(guān)資源,如果未注冊成功,則強制清除通道占用的資源,特別是文件占用符。

關(guān)于通道的注冊邏輯已經(jīng)在手把手教你如何編寫一個NIO客戶端 中已詳細介紹,故接下來重點關(guān)注一下服務(wù)端通道的注冊流程。

2.1.1 服務(wù)端通道初始化流程

AbstractBootstrap 的 init 方法是一個抽象方法,具體有其子類實現(xiàn):

圖片

服務(wù)端通道的初始化代碼由ServerBootstrap的init方法。


圖片

Step1:首先將通過ServerBootstrap設(shè)置的選項與附加選項初始化到通道中。


圖片

Step2:init 方法的關(guān)鍵點:將 handler 方法設(shè)置的事件鏈,同時新增 ServerBootstrapAcceptor 事件處理方法加入到 NioServerSocketChannel 的事件鏈,但并沒有把 childHandler 中添加的事件鏈添加到NioServerSocketChannel。

讀者朋友們,請停下來思考一下,為什么會這樣?從現(xiàn)在可以肯定的是 handler 方法定義的事件處理方法將在與 NioServerSocketChannel 相關(guān)的事件發(fā)生時其作用。

要解開這個謎題,我們有必要來看看 ServerBootstrapAcceptor 是如何工作的。

2.1.2 ServerBootstrapAcceptor 詳解

ServerBootstrapAcceptor 類圖如下所示:

圖片

ServerBootstrapAcceptor方法只實現(xiàn)了inbound事件的channelRead事件。在詳細探究它之前先看看屬性:

  • EventLoopGroup childGroup
    事件執(zhí)行器組,ServerBootstrap設(shè)置的從Reactor線程組,即Work線程組。

  • ChannelHandler childHandler
    ServerBootstrap#childHandler 設(shè)置的事件處理器,也就是用戶定義的事件處理器。

接下來探究其 channelRead 方法的實現(xiàn)邏輯:

圖片

Step1:channelRead竟然傳入的是一個Channel,那這個Channel對象是NioSocketChannel嗎?

是的,原來當 OP_ACCEPT 事件觸發(fā)后,Server端會通過調(diào)用ServerSocketChannel 的 accept()方法,將返回一個 NioSocketChannel,讀寫操作的載體,在NIO中負責數(shù)據(jù)的讀寫。

Step2:將通過 childHandler 定義的事件處理器綁定到 NioSocketChannel。

最終完成 NioServerSocketChannel 與 NioSocketChannel 的初始化與事件綁定。

關(guān)于 NioSocketChannel 詳細的初始化流程蘊含在 ChannelInitializer,其機制已經(jīng)在 手把手教你如何編寫一個NIO客戶端 中詳細介紹。

2.2 NIO綁定機制

在通道完成初始化與注冊后,服務(wù)端需要進行端口綁定,由 AbstractBootstrap 的 doBind0 方法實現(xiàn)。

圖片

bind 的核心實現(xiàn)最終是調(diào)用 Channel 的 bind 方法,最終由 AbstractChannel 類實現(xiàn):


圖片

bind事件將傳播,根據(jù)Netty事件傳播機制,bind 屬于 ChannelOutbound事件,最終將調(diào)用 HeadContext的bind方法,最終將調(diào)用Unsafe的bind方法,更加具體是調(diào)用 AbstractChannel的內(nèi)部類AbstractUnsafe的bind方法,其代碼如下所示:


圖片

doBind 方法是一個抽象方法,NIO服務(wù)端的實現(xiàn):NioServerSocketChannel。


圖片

即最終通過調(diào)用NIO底層NioServerSocketChannel 的 bind 方法完成服務(wù)端通道的綁定操作,即實現(xiàn)服務(wù)端在特定端口監(jiān)聽客戶客戶端的連接請求。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容