Netty由淺入深(一)

下文文字說明來自:Netty 實戰(精髓)》是對 Norman Maurer 的 《Netty in Action》的一個精簡。

https://github.com/waylau/essential-netty-in-action

Netty 快速入門

下面枚舉所有的 Netty 應用程序的基本構建模塊,包括客戶端和服務器。

BOOTSTRAP

Netty 應用程序通過設置 bootstrap(引導)類的開始,該類提供了一個 用於應用程序網絡層配置的容器。

CHANNEL

底層網絡傳輸 API 必須提供給應用 I/O操作的接口,如讀,寫,連接,綁定等等。對於我們來說,這是結構幾乎總是會成爲一個“socket”。 Netty 中的接口 Channel 定義了與 socket 豐富交互的操作集:bind, close, config, connect, isActive, isOpen, isWritable, read, write 等等。 Netty 提供大量的 Channel 實現來專門使用。這些包括 AbstractChannel,AbstractNioByteChannel,AbstractNioChannel,EmbeddedChannel, LocalServerChannel,NioSocketChannel 等等。

CHANNELHANDLER

ChannelHandler 支持很多協議,並且提供用於數據處理的容器。我們已經知道 ChannelHandler 由特定事件觸發。 ChannelHandler 可專用於幾乎所有的動作,包括將一個對象轉爲字節(或相反),執行過程中拋出的異常處理。

常用的一個接口是 ChannelInboundHandler,這個類型接收到入站事件(包括接收到的數據)可以處理應用程序邏輯。當你需要提供響應時,你也可以從 ChannelInboundHandler 沖刷數據。一句話,業務邏輯經常存活於一個或者多個 ChannelInboundHandler。

CHANNELPIPELINE

ChannelPipeline 提供了一個容器給 ChannelHandler 鏈並提供了一個API 用於管理沿着鏈入站和出站事件的流動。每個 Channel 都有自己的ChannelPipeline,當 Channel 創建時自動創建的。 ChannelHandler 是如何安裝在 ChannelPipeline? 主要是實現了ChannelHandler 的抽象 ChannelInitializer。ChannelInitializer子類 通過 ServerBootstrap 進行註冊。當它的方法 initChannel() 被調用時,這個對象將安裝自定義的 ChannelHandler 集到 pipeline。當這個操作完成時,ChannelInitializer 子類則 從 ChannelPipeline 自動刪除自身。

EVENTLOOP

EventLoop 用於處理 Channel 的 I/O 操作。一個單一的 EventLoop通常會處理多個 Channel 事件。一個 EventLoopGroup 可以含有多於一個的 EventLoop 和 提供了一種迭代用於檢索清單中的下一個。

CHANNELFUTURE

Netty 所有的 I/O 操作都是異步。因爲一個操作可能無法立即返回,我們需要有一種方法在以後確定它的結果。出於這個目的,Netty 提供了接口 ChannelFuture,它的 addListener 方法註冊了一個 ChannelFutureListener ,當操作完成時,可以被通知(不管成功與否)。

更多關於 ChannelFuture

想想一個 ChannelFuture 對象作爲一個未來執行操作結果的佔位符。何時執行取決於幾個因素,因此不可能預測與精確。但我們可以肯定的是,它會被執行。此外,所有的操作返回 ChannelFuture 對象和屬於同一個 Channel 將在以正確的順序被執行,在他們被調用後。

 

Channel, Event 和 I/O

Netty 是一個非阻塞、事件驅動的網絡框架。Netty 實際上是使用 Threads(多線程)處理 I/O 事件,對於熟悉多線程編程的讀者可能會需要關注同步代碼。這樣的方式不好,因爲同步會影響程序的性能,Netty 的設計保證程序處理事件不會有同步。圖 Figure 3.1 展示了,你不需要在 Channel 之間共享 ChannelHandler 實例的原因:

Figure 3.1

 

該圖顯示,一個 EventLoopGroup 具有一個或多個 EventLoop。想象 EventLoop 作爲一個 Thread 給 Channel 執行工作。 (事實上,一個 EventLoop 是勢必爲它的生命週期一個線程。)

當創建一個 Channel,Netty 通過 一個單獨的 EventLoop 實例來註冊該 Channel(並同樣是一個單獨的 Thread)的通道的使用壽命。這就是爲什麼你的應用程序不需要同步 Netty 的 I/O操作;所有 Channel 的 I/O 始終用相同的線程來執行。

+

我們將在第15章進一步討論 EventLoop 和 EventLoopGroup。

什麼是 Bootstrapping 爲什麼要用

Bootstrapping(引導) 是 Netty 中配置程序的過程,當你需要連接客戶端或服務器綁定指定端口時需要使用 Bootstrapping。

如前面所述,Bootstrapping 有兩種類型,一種是用於客戶端的Bootstrap,一種是用於服務端的ServerBootstrap。不管程序使用哪種協議,無論是創建一個客戶端還是服務器都需要使用“引導”。

面向連接 vs. 無連接

請記住,這個討論適用於 TCP 協議,它是“面向連接”的。這樣協議保證該連接的端點之間的消息的有序輸送。無連接協議發送的消息,無法保證順序和成功性

兩種 Bootstrapping 之間有一些相似之處,也有一些不同。Bootstrap 和 ServerBootstrap 之間的差異如下:

Table 3.1 Comparison of Bootstrap classes

分類 Bootstrap ServerBootstrap
網絡功能 連接到遠程主機和端口 綁定本地端口
EventLoopGroup 數量 1 2

Bootstrap用來連接遠程主機,有1個EventLoopGroup

ServerBootstrap用來綁定本地端口,有2個EventLoopGroup

事件組(Groups),傳輸(transports)和處理程序(handlers)分別在本章後面講述,我們在這裏只討論兩種"引導"的差異(Bootstrap和ServerBootstrap)。第一個差異很明顯,“ServerBootstrap”監聽在服務器監聽一個端口輪詢客戶端的“Bootstrap”或DatagramChannel是否連接服務器。通常需要調用“Bootstrap”類的connect()方法,但是也可以先調用bind()再調用connect()進行連接,之後使用的Channel包含在bind()返回的ChannelFuture中。

一個 ServerBootstrap 可以認爲有2個 Channel 集合,第一個集合包含一個單例 ServerChannel,代表持有一個綁定了本地端口的 socket;第二集合包含所有創建的 Channel,處理服務器所接收到的客戶端進來的連接。下圖形象的描述了這種情況:

Figure 3.2 Server with two EventLoopGroups

 

與 ServerChannel 相關 EventLoopGroup 分配一個 EventLoop 是 負責創建 Channels 用於傳入的連接請求。一旦連接接受,第二個EventLoopGroup 分配一個 EventLoop 給它的 Channel。

ChannelHandler 和 ChannelPipeline

ChannelPipeline 是 ChannelHandler 鏈的容器。

在許多方面的 ChannelHandler 是在您的應用程序的核心,儘管有時它 可能並不明顯。ChannelHandler 支持廣泛的用途,使它難以界定。因此,最好是把它當作一個通用的容器,處理進來的事件(包括數據)並且通過ChannelPipeline。下圖展示了 ChannelInboundHandler 和 ChannelOutboundHandler 繼承自父接口 ChannelHandler。

Figure 3.3 ChannelHandler class hierarchy

 

Netty 中有兩個方向的數據流,圖3.4 顯示的入站(ChannelInboundHandler)和出站(ChannelOutboundHandler)之間有一個明顯的區別:若數據是從用戶應用程序到遠程主機則是“出站(outbound)”,相反若數據時從遠程主機到用戶應用程序則是“入站(inbound)”。

爲了使數據從一端到達另一端,一個或多個 ChannelHandler 將以某種方式操作數據。這些 ChannelHandler 會在程序的“引導”階段被添加ChannelPipeline中,並且被添加的順序將決定處理數據的順序。

Figure 3.4 ChannelPipeline with inbound and outbound ChannelHandlers

 

圖 3.4 同樣展示了進站和出站的處理器都可以被安裝在相同的 pipeline 。本例子中,如果消息或任何其他入站事件被讀到,將從 pipeline 頭部開始,傳遞到第一個 ChannelInboundHandler。該處理器可能會或可能不會實際修改數據,取決於其特定的功能,在這之後 該數據將被傳遞到鏈中的下一個 ChannelInboundHandler。最後,將數據 到達 pipeline 的尾部,此時所有處理結束。

數據的出站運動(即,數據被“寫入”)在概念上是相同的。在這種情況下的數據從尾部流過 ChannelOutboundHandlers 的鏈,直到它到達頭部。超過這點,出站數據將到達的網絡傳輸,在這裏顯示爲一個 socket。通常,這將觸發一個寫入操作。

+

更多 Inbound 、 Outbound Handler

在當前的鏈(chain)中,事件可以通過 ChanneHandlerContext 傳遞給下一個 handler。Netty 爲此提供了抽象基類ChannelInboundHandlerAdapter 和 hannelOutboundHandlerAdapter,用來處理你想要的事件。 這些類提供的方法的實現,可以簡單地通過調用 ChannelHandlerContext 上的相應方法將事件傳遞給下一個 handler。在實際應用中,您可以按需覆蓋相應的方法即可。

所以,如果出站和入站操作是不同的,當 ChannelPipeline 中有混合處理器時將發生什麼?雖然入站和出站處理器都擴展了 ChannelHandler,Netty 的 ChannelInboundHandler 的實現 和 ChannelOutboundHandler 之間的是有區別的,從而保證數據傳遞只從一個處理器到下一個處理器保證正確的類型。

當 ChannelHandler 被添加到的 ChannelPipeline 它得到一個 ChannelHandlerContext,它代表一個 ChannelHandler 和 ChannelPipeline 之間的“綁定”。它通常是安全保存對此對象的引用,除了當協議中的使用的是不面向連接(例如,UDP)。而該對象可以被用來獲得 底層 Channel,它主要是用來寫出站數據。

還有,實際上,在 Netty 發送消息有兩種方式。您可以直接寫消息給 Channel 或寫入 ChannelHandlerContext 對象。主要的區別是, 前一種方法會導致消息從 ChannelPipeline的尾部開始,而 後者導致消息從 ChannelPipeline 下一個處理器開始。

近距離觀察 ChannelHandler

正如我們之前所說,有很多不同類型的 ChannelHandler 。每個 ChannelHandler 做什麼取決於其超類。 Netty 提供了一些默認的處理程序實現形式的“adapter(適配器)”類。這些旨在簡化開發處理邏輯。我們已經看到,在 pipeline 中每個的 ChannelHandler 負責轉發事件到鏈中的下一個處理器。這些適配器類(及其子類)會自動幫你實現,所以你只需要實現該特定的方法和事件。

爲什麼用適配器?

有幾個適配器類,可以減少編寫自定義 ChannelHandlers ,因爲他們提供對應接口的所有方法的默認實現。(也有類似的適配器,用於創建編碼器和解碼器,這我們將在稍後討論。)這些都是創建自定義處理器時,會經常調用的適配器:ChannelHandlerAdapter、ChannelInboundHandlerAdapter、ChannelOutboundHandlerAdapter、ChannelDuplexHandlerAdapter

下面解釋下三個 ChannelHandler 的子類型:編碼器、解碼器以及 ChannelInboundHandlerAdapter 的子類SimpleChannelInboundHandler

編碼器、解碼器

當您發送或接收消息時,Netty 數據轉換就發生了。入站消息將從字節轉爲一個Java對象;也就是說,“解碼”。如果該消息是出站相反會發生:“編碼”,從一個Java對象轉爲字節。其原因是簡單的:網絡數據是一系列字節,因此需要從那類型進行轉換。

不同類型的抽象類用於提供編碼器和解碼器的,這取決於手頭的任務。例如,應用程序可能並不需要馬上將消息轉爲字節。相反,該​​消息將被轉換 一些其他格式。一個編碼器將仍然可以使用,但它也將衍生自不同的超類,

在一般情況下,基類將有一個名字類似 ByteToMessageDecoder 或 MessageToByteEncoder。在一種特殊類型的情況下,你可能會發現類似 ProtobufEncoder 和 ProtobufDecoder,用於支持谷歌的 protocol buffer。

+

嚴格地說,其他處理器可以做編碼器和解碼器能做的事。但正如適配器類簡化創建通道處理器,所有的編碼器/解碼器適配器類 都實現自 ChannelInboundHandler 或 ChannelOutboundHandler。

對於入站數據,channelRead 方法/事件被覆蓋。這種方法在每個消息從入站 Channel 讀入時調用。該方法將調用特定解碼器的“解碼”方法,並將解碼後的消息轉發到管道中下個的 ChannelInboundHandler。

出站消息是類似的。編碼器將消息轉爲字節,轉發到下個的 ChannelOutboundHandler。

SimpleChannelHandler

也許最常見的處理器是接收到解碼後的消息並應用一些業務邏輯到這些數據。要創建這樣一個 ChannelHandler,你只需要擴展基類SimpleChannelInboundHandler 其中 T 是想要進行處理的類型。這樣的處理器,你將覆蓋基類的一個或多個方法,將獲得被作爲輸入參數傳遞所有方法的 ChannelHandlerContext 的引用。

在這種類型的處理器方法中的最重要是 channelRead0(ChannelHandlerContext,T)。在這個調用中,T 是將要處理的消息。 你怎麼做,完全取決於你,但無論如何你不能阻塞 I/O線程,因爲這可能是不利於高性能。

阻塞操作

I/O 線程一定不能完全阻塞,因此禁止任何直接阻塞操作在你的 ChannelHandler, 有一種方法來實現這一要求。你可以指定一個 EventExecutorGroup 當添加 ChannelHandler 到ChannelPipeline。此 EventExecutorGroup 將用於獲得EventExecutor,將執行所有的 ChannelHandler 的方法。這EventExecutor 將從 I/O 線程使用不同的線程,從而釋放EventLoop。

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