標籤:
本章內容包括
1)用戶端和伺服器端的Bootstrapping
2)在一個Channel中的Bootstrapping用戶端
3)增加ChannelHandler
4)使用ChannelOptions和attributes
已經深入地學習過了ChannelPipeline,ChannelHandler,codec等類,你接下來的可能會問:如何將這些組件組裝起來增加到你的應用中去?
答案是:“Bootstrapping”,其實到目前為止,我們已經模糊地接觸過幾次這個組件了,現在是時候給它下一個明確的定義了,簡單地陳述一下,Bootstrapping,顧名思義,就是通過配置可以將你的程式運行起來的意思,也許這個配置的細節比它的概念可能會難一點,特別是啟動的是一個網路項目的時候
與Netty架構架構的理念一致,Netty處理Bootstrapping時進行了封裝,使其與你的應用解耦,使你的伺服器端和用戶端都與網路層解耦,不要再注意配置底層實現的細節,你會看見,所有的架構元件連線組合都是在幕後進行的,你會發現Bootstrapping就像你在拼圖的時候,缺失的那部分,當你將其放入正確的拼圖位置的時候,你的Netty應用的整個藍圖就會完成了
8.1 Bootstrap classes
bootstrapping的類是繼承與一個抽象的父類方法,兩個具體的實作類別,如8.1圖所示
可以將這兩個具體的實作類別想象成伺服器端和用戶端啟動類,這樣可以協助你區分他們所支援的不同的應用功能點,顧名思義,一個伺服器端致力於建立一個父類channel來接收來自用戶端的串連,並且建立一個子channel用來服務於兩者之間的對話,然而對於用戶端來說,一個用戶端最多接收一個單獨的非父類的channel用來進行網路互動,我們也會意識到,這種模式也是使用於無連線類型的傳輸類型的UDP協議,因為他們不需要為每次的串連建立一個channel
我們之前幾個章節學習的一些Netty組件也會在啟動的過程中加入進來,他們中的一些成員將會在伺服器端和用戶端都會被用到,bootstrapping將一些對於所有應用類型的公用實現方法放到了AbstractBootstrap中,而相對應的一些具體的操作放在了Bootstrap和ServerBootstrap中分別用來服務用戶端和伺服器端
在接下來的章節中,我們將詳細地討論這兩個類的細節,我們先從比較簡單的Bootstrap類講起
TIPS:為什麼bootstrap的類實現了Cloneable的介面呢?
你有時候需要建立多個channel,這些channel有著相同或者相似的配置,為了在不需要建立一個新的bootstrap執行個體來為每一個channel一一配置的前提下去支援這種功能模式,AbstractBootstrap就被標記了Cloneable,在一個已經配置好的bootstrap的基礎上調用一個clone方法將會返回一個可以立即投入使用的bootstrap執行個體
請注意,這隻是對Bootstrap的EventLoopGroup的淺拷貝,所以EventLoopGroup將在所有的channel中共用,這個是可以接受的,因為大部分的clone的channel它的生命週期經常是很短暫的,一個經典的案例就是建立一個Http請求
一個AbstractBootstrap的完整聲明是這樣的:
在這個聲明中,子類B是父類中的一個型別參數,這樣做的好處就是在運行時這個對象的引用可以被返回,這樣就可以支援流式編程模式
子類的可以如下定義
8.2 Bootstrapping clients and connectionless protocols
Bootstrap被用在用戶端或者使用在不需連線的應用中,表8.1給出了這個類的一些概述,這些裡面的很多方法是繼續於AbstractBootstrap類
下一個小節我們將會一步步地分析用戶端的bootstrapping,我們也會討論在選擇可利用的組件實現的時候,相容性的一些問題
8.2.1 Bootstrapping a client
Bootstrap是用來為用戶端建立一個channel並且為應用可以利用不需連線的協議,8.2所示
下面的代碼清單展示了Bootstrap的用戶端使用NIO的TCP協議
這個例子很好地展示了流式編程風格,所有的方法除了connect方法之外都是通過前面方法返回的原始對象的引用鏈式調用的
8.2.2 Channel and EventLoopGroup compatibility
下面的目錄是來自於io.netty.channel包的結構,你可以清晰地看見包名與包下對應的類的首碼名是一樣的,這些類名是用來關聯EventLoopGroup和Channel,並以NIO和OIO兩種方式去實現
相容性是必須去維護的,你不能混合的使用這些不同命名首碼的組件,例如將NioEventLoopGroup和OioSocketChannel混合使用,下面的代碼清單展示了這樣混合使用的嘗試
這個代碼塊會引起IllegalStateException的異常,因為它混合了不相容的傳輸方式
TIPS:關於更多的IllegalStateException
在啟動bootstrap時,你在調用bind或者connect方法之前,你必須使用如下的方法設定那些啟動時的必要參數,例如group方法,channel方法或者channelFactory方法,和handler方法,如果做這些操作失敗的話就會拋出IllegalStateException異常,handler方法特別重要,因為它是用來配置ChannelPipeline的
8.3 Bootstrapping servers
我們先列出ServerBootstrap的API,來給我們學習ServerBootstrap時做一個一個知識總覽,然後我們就一步步地講解啟動伺服器端時的一些小細節,並且講解一些相關知識點,包括一個特殊的案例:從一個服務端的channel去啟動一個用戶端
8.3.1 The ServerBootstrap class
表8.2列出了ServerBootstrap的幾個方法
下一個章節,我們將講解server端的bootstrapping的一些細節
8.3.2 Bootstrapping a server
你可能已經看到表8.2中列出的一些方法並沒有在表8.1中出現過,例如childHandler(),childAttr(),childOption()方法,這些是典型的支援伺服器端操作的方法,具體來說,ServerChannel的實現負責用於建立一個子Channel,這個Channel負責接收一些連結,然後ServerBootstrap用來啟動ServerChannel,提供了這些方法來簡化一些連結channel的配置任務
圖8.3展示了一個ServerBootstrap用bind方法來建立一個ServerChannel的情形,並且ServerChannel用來管理一定數量的子Channel
下面的代碼清單實現了圖8.3中伺服器端啟動的具體實現
8.4 Bootstrapping clients from a Channel
假設你的伺服器端在需要處理一個用戶端請求時,需要將伺服器端的角色定位一個第三方的用戶端的角色,這種情形是很容易經常發生的,例如Proxy 伺服器,這種情況下你需要與一個組織上的已經存在的系統再次進行互動,例如web server或者資料庫,在這種情況下,你就需要從ServerChannel中啟動一個用戶端的Channel
你可以按照小節8.2.1中描述的那樣建立一個Bootstrap,但是這並不是一個最有效解決方案,因為它需要你再次定義另一個EventLoop用來管理新的用戶端的channel,這就會需要新增額外的線程,當進行在原有的Channel和用戶端channel之間資料互動的時候,就會產生線程之間的環境切換
一個更好的解決方案就是分享接收的Channel的EventLoop,通過將EventLoop傳遞給Bootstrap的group方法,因為被分配到同一個EventLoop所有的Channel使用同一個線程,這樣就阻止了更多額外進程的建立和我們之前提及的環境切換的問題,這種分享EventLoop的方案說明圖如下8.4圖展示:
實現EventLoop共用需要通過調用group方法來設定EventLoop,如下面的代碼清單所示:
關於共用EventLoop這個主題我們在這個小節已經討論過了,這個解決防範反映了我們在編寫Netty應用的一個思路和應該遵守的方針:儘可能的重複利用EventLoop來減少建立線程的開銷
8.5 Adding multiple ChannelHandlers during a bootstrap
在我們之前的展示的所有代碼中,我們在bootstrap處理一個單獨的ChannelHandler的時候只調用了handler和childhandler方法,這對於一些簡單的應用來說是很高效的,但是這並不能滿足我們的一些比較複雜的需求,例如,如果一個應用需要支援多協議,那麼就需要多個ChannelHandler了,來替代哪些笨重冗餘的一些類
我們之前提及過很多次,你可以將你需要的所有ChannelHandler鏈式的放入到ChannelPipeline中去,但是如果在啟動過程中只能設定一個ChannelHandler時怎麼辦的?
為瞭解決這個使用案例,Netty提供了一個特殊的父類ChannelInboundHandlerAdapter
它定義了如下的方法:
這個方法提供了一個簡單的途徑可以將多個ChannelHandler添加到ChannelPipeline中,你可以簡單地通過提供一個ChannelInitializer的具體實現給bootstrap,當EventLoop註冊到Channel時,你自訂的initChannel的方法就會被調用,當所有的返回之後,ChannelInitializer執行個體將會將自己從ChannelPipeline中移除
下面的代碼清單定義了一個ChannelInitializerImpl類,且將其註冊到bootstrap的childHandler方法上,你將會看到這個複雜操作可以被這麼直白易懂地編寫出
如果你的應用需要利用多個ChannelHandler,你可以自訂你自己的ChannelInitializer來將其安裝到管道中
8.6 Using Netty ChannelOptions and attributes
當為每一個建立的channel一個個手動設定時將會變得非常冗餘,幸運的是,我們不需要這麼做,相反,你可以用option方法來將ChannelOptions應用到bootstrap中,你提供的一些屬性值將會自動應用到bootstrap建立的所有channel中去,ChannelOptions中可以配置一些底層的串連細節例如keep-alive或者逾時配置和buffer配置
Netty應用經常需要與一些項目中專屬應用互動,Netty中的一些組件可能會被用在Netty項目之外的項目中運用到,例如Channel組件,在這種情境中,一些常用的屬性和資料將會變得不可用,Netty提供了AttributeMap抽象,這個集合是由channel和bootstrap類提供的,和AttributeKey<T>這是一個通用的類用來協助你插入和檢索一些屬性值,有了這些工具類,你可以在客戶和服務端Channel中安全地互動任何形式的資料
思考一下下面的例子,當一個伺服器端的應用需要追蹤使用者和channel之間關係的時候,你可以通過儲存一個user的id做為一個channel的屬性來實現這個功能,這個簡單的小功能可以依據他們的id來路由到他們的用戶端資訊,或者來關閉用戶端串連當channel中長時間沒有任何活動的時候
下面的代碼清單向你展示了如何使用ChannelOption來配置一個Channel這個屬性中儲存了一個Integer類型的變數
8.7 Bootstrapping DatagramChannels
先前的bootstrap程式碼範例都是使用ServerChannel,這是基於TCP的,但是有時候Bootstrap也可以用於不需連線的協議,Netty提供了各種各樣的DatagramChannel的具體實現來達到這個目的,唯一的不同就是你不用connect方法而是使用bind方法,如下面代碼展示:
8.8 Shutdown
Bootstrapping使你的應用進入啟動並執行狀態,但是遲早你都需要將應用優雅地關閉,當然,你可以使用JVM處理,讓所有的應用退出,但這不能滿足“優雅”二字,這兩個字代表了可以很乾淨地去釋放所有的資源,其實在關閉Netty應用的時候,並沒有太多需要特別關注的,只需要有幾點細節需要牢記在心
首先,你需要先關閉EventLoopGroup,這可以處理任何後續的事件和任務,接下來就是釋放所有的存活的線程,你只需要調用EventLoopGroup的shutdownGracefully方法,這個方法將會返回一個Future對象,當關閉的操作完成的時候,將會通知Future對象,注意到shutdownGracefully這個方法也是非同步作業,所以你需要要麼阻塞當前的操作指導關閉操作的完成,要麼註冊一個監聽器來監聽Future對象來確定關閉是否完成
下面的代碼清單展示了如何優雅地關閉
另外,你可以在調用EventLoopGroup的shutdownGracefully方法之前調用Channel.close方法來清晰地關閉所有存活的channel對象,但是在所有的案例中,請一定要關閉EventLoopGroup對象本身
8.9 Summary
在這個章節中,我們學習了如何啟動Netty的用戶端和伺服器端,包括使用不需連線的協議,我們使用了一些具體的案例來講解,這些案例包括在伺服器端的應用去啟動一個用戶端的channel,還包括在啟動過程中如何使用ChannelInitializer來處理安裝多個channel
你也瞭解到在channel中進行配置一些具體的選項,如何使用attribute來將一些附加的資訊添加到channel中,最後我們學習到如何優雅地關閉一個應用來按順序地去釋放所有的資源
在下一個章節,我們將學習Netty提供的一些工具,用來測試你自訂的ChannelHandler的實現
Netty in Action (十八) 第八章節 Bootstrapping