Netty實戰三之Netty的組件和設計

有關Netty,我們可以從兩個視角來討論Netty:類庫的視角以及框架的視角,對於使用Netty編寫高效的、可重用的和可維護的代碼來說,兩者缺一不可。

Netty解決了兩個響應的關注領域,可以大致標誌爲技術的和體系結構的。

它基於Java NIO的異步和事件驅動的實現,保證了高負載下應用程序性能的最大化和可伸縮性。其次,Netty也包含了一組設計模式,將應用程序邏輯從網絡層解耦,簡化開發過程,同時也最大限度地提高了可測試性、模塊化以及代碼的可重用性。

Netty網絡抽象的代表:

——Channel:Socket

——EventLoop:控制流、多線程處理、併發

——ChannelFuture:異步通知

1、Channel接口

Netty的Channel接口所提供的的API,大大降低了直接使用Socket類的複雜性,此外,Channel也擁有許多預定義的、專門化實現的廣泛類層次結構的根。

——EmbeddedChannel

——LocalServerChannel

——NioDatagramChannel

——NioSctpChannel

——NioSocketChannel

2、EventLoop接口

EventLoop定義了Netty的核心抽象,用於處理連接的生命週期中所發生的事件。圖3-1在高層次上說明了Channel、EventLoop、Thread以及EventLoopGroup之間的關係。

Netty實戰三之Netty的組件和設計

——EventLoopGroup包含一個或者多個EventLoop

——一個EventLoop在它的生命週期內只和一個Thread綁定

——所有由EventLoop處理的I/O事件都將在它專有的Thread上被處理

——一個Channel在它的生命週期內只註冊於一個EventLoop

——一個EventLoop可能會被分配給一個或多個Channel

注意,在這種設計中,一個給定Channel的I/O操作都是由相同的Thread執行的,實際上消除了對於同步的需要。

3、ChannelFuture接口

Netty中所有的I/O操作都是異步的,因爲一個操作可能不會立即返回,所以我們需要一種用於在之後的某個時間點確定其結果的方法,爲此,Netty提供了ChannelFuture接口,其addListener()方法註冊了一個ChannelFutureListener,以便在某個操作完成時(無論是否成功)得到通知。

關於ChannelFuture的更多討論——可以將ChannelFuture看作是將來要執行的結果的佔位符,它究竟什麼時候被執行則可能取決於若干的因素,因此不可能準確地預測,但是可以肯定的是它將會被執行,此外,所有屬於同一個Channel的操作都被保證其將以它們被調用的順序被執行。

4、ChannelHandler接口

Netty的主要組件是ChannelHandler,它充當了所有處理入站和出站數據的應用程序邏輯的容器。因爲ChannelHandler的方法是由網絡事件觸發的。事實上,ChannelHandler可專門用於幾乎任何類型的動作,例如將數據從一種格式轉換爲另一種格式,或者處理轉換過程中所拋出的異常。

例如,ChannelInboundHandler 是一個你將會經常實現的子接口,這種類型的ChannelHandler接收入站事件和數據,這些數據隨後將會被你的應用程序的業務邏輯所處理。當你要給連接的客戶端發送響應時,也可以從ChannelInboundHandler沖刷數據。你的應用程序的業務邏輯通常駐留在一個或者多個ChannelInboundHandler中。

5、ChannelPipeline接口

ChannelPipeline爲ChannelHandler鏈提供了容器,並定義了用於在該鏈上傳播入站和出站事件流的API。當Channel被創建時,它會被自動地分配到它專屬的ChannelPipeline。

——一個ChannelInitializer的實現被註冊到了ServerBootstrap中; ——當ChannelInitializer.initChannel()方法被調用時,ChannelInitializer將在ChannelPipeline中安裝一組自定義的ChannelHandler; ——ChannelInitializer將它自己從ChannelPipeline中移除;

ChannelHandler是專爲支持廣泛的用途而設計的,可以將它看作是處理往來ChannelPipeline事件(包括數據)的任何代碼的通用容器。圖2-3說明了這一點,其展示了從ChannelHandler派生的ChannelInboundHandler和ChannelOutboundHandler接口。

Netty實戰三之Netty的組件和設計

使得事件流經ChannelPipeline是ChannelHandler的工作,它們是在應用程序的初始化或者引導階段被安裝的,這些對象接收事件、執行它們所實現的處理邏輯,並將數據傳遞給鏈中的下一個ChannelHandler。它們的執行順序是由它們被添加的順序所決定的。ChannelPipeline就是ChannelHandler的編排順序。

圖3-3說明了一個Netty應用程序中入站和出站數據流之間的區別。從一個客戶端應用程序的角度來看,如果事件的運動方向是從客戶端到服務器端,那麼我們稱這些事件爲出站,反之則稱爲入站。

Netty實戰三之Netty的組件和設計

由上圖可看出入站和出站ChannelHandler可以被安裝到同一個ChannelPipeline中,事件的讀取將從ChannelPipeline的頭部開始流動,並被傳遞給第一個ChannelInboundHandler,這個ChannelHandler不一定會實際修改數據,具體取決於它的具體功能,在之後,數據將會傳遞給鏈中的下一個ChannelInboundHandler,最終,數據將會到達ChannelPipeline的尾端,即所有處理結束。

出站事件也相同的流程,在到達鏈的頭部之後,出站數據將會到達網絡傳輸層,這裏顯示爲Socket,通常情況下,這將觸發一個寫操作。

關於入站和出站ChannelHandler的更多討論

通過使用作爲參數傳遞到每個方法的ChannelHandlerContext,事件可以被傳遞給當前ChannelHandler鏈中的下一個ChannelHandler。因爲你有時會忽略那些不感興趣的事件,所以Netty提供了抽象基類ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter。通過調用ChannelHandlerContext上的對應方法,每個都提供了簡單地將事件傳遞給下一個ChannelHandler的方法的實現,隨後,你可以通過重寫你所感興趣的那些方法來擴展這些類。

雖然ChannelInboundHandler和ChannelOutboundHandler都擴展自ChannelHandler,但是Netty能區分ChannelInboundHandler實現和ChannelOutboundHandler實現,並確保數據只會在具有相同定向類型的兩個ChannelHandler之間傳遞。

當ChannelHandler被添加到ChannelPipeline時,它將被分配一個ChannelHandlerContext,其代表了ChannelHandler和ChannelPipeline之間的綁定。雖然這個對象可以被用於獲取底層的Channel,但是它主要還是被用於寫出站數據。

在Netty中,有兩種發送消息的方式。你可以直接寫到Channel中,也可以寫到ChannelHandler相關聯的ChannelHandlerContext對象中,前一種方式將會導致消息從ChannelPipeline的尾端開始流動,而後者將導致消息從ChannelPipeline中的下一個ChannelHandler開始流動。

6、深入瞭解ChannelHandler

有許多不同類型的ChannelHandler,它們各自的功能主要取決於它們的超類。Netty以適配器類的形式提供了大量默認的ChannelHandler實現,其旨在簡化應用程序處理邏輯的開發過程,ChannelPipeline中的每個ChannelHandler將負責把事件轉發到鏈中的下一個ChannelHandler,這些適配器將自動執行這個操作,所以你可以只重寫那些你想要特殊處理的方法和事件。

爲什麼需要適配器類

有些適配器類可以將編寫自定義的ChannelHandler所需要的努力降到最低限度,因爲他們提供了定義在對應接口中的所有方法的默認實現。

——ChannelHandlerAdapter

——ChannelInboundHandlerAdapter

——ChannelOutboundHandlerAdapter

——ChannelDuplexHandler

7、編碼器和解碼器

當你通過Netty發送或者接收一個消息的時候,就將會發生一次數據轉換。入站就解碼爲Java對象,出站就編碼爲字節。(網絡數據總是一系列的字節)

所有由Netty提供的編碼器、解碼器適配器類都實現了ChannelOutboundHandler或者ChannelInboundHandler接口。

你將會發現對於入站數據,channelRead方法、事件已經被重寫了,對於每個從入站Channel讀取消息,這個方法都將會被調用,隨後,它將調用由預置解碼器所提供的的decode()方法,並將已解碼的字節轉發給ChannelPipeline中的下一個ChannelInboundHandler。

出站消息的模式是相反方向的:編碼器將消息轉換爲字節,並將它們轉發給下一個ChannelOutboundHandler。

8、抽象類SimpleChannelInboundHandler

最常見的情況是,你的應用程序會利用一個ChannelHandler來接收解碼消息,並對該數據應用業務邏輯。要創建一個這樣的ChannelHandler,你只需要擴展基類SimpleChannelInboundHandler,其中T是你要處理的消息的Java類型,在這個ChannelHandler中,你將需要重寫基類的一個或者多個方法,並且獲取一個到ChannelHandlerContext的引用,這個引用將作爲輸入參數傳遞給ChannelHandler的所有方法。

在這種類型的ChannelHandler中,最重要的方法是channelRead0(ChannelHandlerContext,T)。除了要求不要阻塞當前的I/O線程之外,其具體實現完全取決於你。

9、引導

Netty的引導類爲應用程序的網絡層配置提供了容器,這涉及將一個進程綁定到某個指定的端口,或者將一個進程連接到另一個運行在某個指定主機的指定端口上的進程。

“服務器”和“客戶端”實際上表示了不同的網絡行爲:是監聽傳入的連接還是建立到一個或者多個進程的連接。

面向連接的協議:請記住,嚴格來說,“連接”這個屬於僅適用於面向連接的協議,如TCP、其保證了兩個連接端點之間消息的有序傳遞。

因此,有兩種類型的引導:一種用於客戶端(簡單稱爲Bootstrap),而另一種(ServerBootstrap)用於服務器。無論你的應用程序使用哪些協議或者處理哪種類型的數據,唯一決定它使用哪種引導類的是它是作爲一個客戶端還是作爲一個服務器。
Netty實戰三之Netty的組件和設計

這兩種類型的引導類之間的第一個區別已經討論過了:ServerBootstrap將綁定到一個端口,因爲服務器必須要監聽連接,而Bootstrap則由想要連接到遠程節點的客戶端應用程序所使用的。

第二個區別可能更加明顯,引導一個客戶端只需要一個EventLoopGroup,但是一個ServerBootstrap則需要兩個(也可以是同一個實例),爲什麼呢?

因爲服務器需要兩組不同的Channel。第一組將只包含一個ServerChannel,代表服務器自身的已綁定到某個本地端口的正在監聽的套接字。而第二組將包含所有已創建的用來處理傳入客戶端連接(對於每個服務器已經接受的連接都有一個)的Channel。
Netty實戰三之Netty的組件和設計

與ServerChannel相關聯的EventLoopGroup將分配一個負責爲傳入連接請求創建Channel的EventLoop。一旦連接被接收,第二個EventLoopGroup就會給它的Channel分配一個EventLoop。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章