天天看點

Netty in action—Netty元件和設計

Channel,EventLoop和ChannelFuture

這三者可以看作是Netty網絡架構的抽象

  • Chennel-Sockets
  • EvnetLoop-流控(Control flow),多線程,并發
  • ChannelFuture - 異步通知

Channel接口

基本的IO操作(bind(),connect(),read(),write())都是基于底層的網絡傳輸。在Java網絡程式設計中,基礎建構是Socket類,但是直接基于Socket進行程式設計會很繁瑣。Netty的Channel接口提供的API能大大地減小直接對Socket進行程式設計的複雜性。另外,Channel又是很多實作了特定需求的Channel的頂層抽象接口,這些Channel接口的實作類有:

  • EmbeddedChannel
  • LocalServerChannel
  • NioDatagramChannel
  • NioSctpChannel
  • NioSocketChannel

EventLoop接口

EventLoop用來處理一個連接配接(connection)生命周期内發生的事件。下圖在高層次上描述了Channel、EventLoop、Thread和EventLoopGroup之間的關系。

這些關系有:

  • EventLoopGroup包含一個或多個EventLoop
  • 一個EvnetLoop執行個體綁定到了一個線程在它(EvnetLoop)的生命周期内。
  • 所有被EventLoop操作的IO事件都是在它專有的線程中處理
  • 一個Channel在它的生命周期中被注冊到一個EventLoop中,然後這個EventLoop就會為這個Channel處理所有的IO操作
  • 一個EventLoop執行個體能注冊多個Channel
  • Netty in action—Netty元件和設計

注意在這種設計中,給定Channel的IO操作都是在同一個線程中執行,無形中就不需要進行同步。

ChannelFuture接口

Netty中的所有IO操作都是異步的。因為一個操作的結果可能不會馬上傳回,是以我們需要一種能夠在未來擷取它的結果的方式。為此,Netty提供了​

​ChannelFuture​

​​,它的​

​addListener()​

​​方法注冊了一個​

​ChannelFutureListener​

​​,通過這個​

​ChannelFutureListener​

​就能在操作完成(不管有沒有成功)時得到通知。

ChannelHandler和ChannelPipeline

ChannelHandler接口

站在應用程式開發者的角度來看,Netty主要的元件就是​

​ChannelHandler​

​​,它作為所有為傳入和傳出資料提供處理的應用業務邏輯的容器。通常實作它(或繼承實作了它的類)來提供業務邏輯。這是可能的因為​

​ChannelHandler​

​​的方法會被網絡事件觸發。實際上,​

​ChannelHandler​

​能被用于任意類型的操作,比如,資料的格式轉換或處理操作過程中抛出的異常。

你通常需要實作的是​

​ChannelHandler​

​​的子接口​

​ChannelInboundHandler​

​​。這個子接口能接收你業務邏輯需要處理的資料或者事件。當你要發送一個回複給用戶端的時候,你能從​

​ChannelInboundHandler​

​​中flush資料。你的業務邏輯通過會依賴于多個​

​ChannelInboundHandler​

​。

ChannelPipeline接口

​ChannelPipeline​

​​為一連串的​

​ChannelHandler​

​​提供容器同時定義了用于傳播鍊中傳入資料和傳出資料事件流的API。​

​Channel​

​​被建立後會自動的配置設定到它自己的​

​ChannelPipeline​

​。

​ChannelHandler​

​​通過如下的步驟被加載到​

​ChannelPipeline​

​中:

  • 一個​

    ​ChannelInitializer​

    ​​的實作注冊到​

    ​ServerBootstrap​

    ​中
  • 當​

    ​ChannelInitializer.initChannel()​

    ​​方法被調用,​

    ​ChannelInitializer​

    ​​裝載了一系列自定義的​

    ​ChannelHandlers​

    ​到pipeline中。
  • ​ChannelInitializer​

    ​​把它自己從​

    ​ChannelPipeline​

    ​​中移除掉

    ​​

    ​ChannelHandler​

    ​​具有廣泛的用途,你能把它想成一個為任何處理在​

    ​ChannelPiple​

    ​​中流通(傳入傳出)的事件(包括資料)的代碼的通用容器(generic container)。下圖說明了​

    ​ChannelInboundHandler​

    ​​和​

    ​ChannelOutboundHandler​

    ​​繼承自​

    ​ChannelHandler​

    ​的關系。
Netty in action—Netty元件和設計

事件通過在初始化或啟動應用期間加載的​

​ChannelHandler​

​​在pipeline中傳遞,​

​ChannelHandler​

​​接受事件并執行在其中實作了的業務邏輯,并将資料傳遞給(handler)鍊中的下一個handler。這些​

​handler​

​根據它們被添加的順序來執行。

Netty in action—Netty元件和設計

上圖展示了Netty應用中輸入(資料)流和輸出(資料)流的差別。對于用戶端來說,從用戶端到伺服器事件就是傳出去(outbound)的反之就是傳進來(inbound)的。

inbound和outbound handler都能安裝到同一個pipeline。如果讀到一條消息或者其他傳入(inbound)事件,它會從這條pipeline的頭部(head)開始然後傳遞給第一個​

​ChannelInboundHandler​

​。這個handler可能會也可能不會真的修改資料,這取決于它的具體函數,然後資料會傳遞給下一個handler。最終,資料回到達pipeline的末尾(tail),此時所有的處理都結束了。

資料傳出(outbound)的移動(也就是資料被寫)與傳入同理。這種情況下,資料從尾部流到頭部。

因為傳出資料和傳入資料是有差別的,你可能想知道當這兩種不同的操作都混在了同一個​

​ChannelPipeline​

​​時會發生什麼。雖然inbound和outbound handler都繼承了​

​ChannelHandler​

​,Netty提供了分别的實作(ChannelInboundHandler好ChannelOutbouondHandler)同時確定資料隻會在同一個方向的handler中傳遞。

當一個​

​ChannelHandler​

​​被添加到​

​ChannelPipeline​

​​中,它會被配置設定一個代表​

​ChannelHandler​

​​和​

​ChannelPipeline​

​​之間綁定關系的一個​

​ChannelHandlerContext​

​​。盡管可以通過這個Context擷取底層的​

​Channel​

​執行個體,但是它主要用來寫傳出資料(write outbound data)。

在Netty中有兩種發送資料的方式。你既可以直接寫到​

​Channel​

​​裡面又可以寫到與​

​ChannelHander​

​​相關的​

​ChannelHandlerContext​

​​中。前者會使資料從​

​ChannelPipeline​

​​的末尾開始傳輸,後者會使資料從​

​ChannelPipeline​

​中下一個handler開始。

更深入的了解ChannelHandler

正如我們前面說到的,Netty提供了不同類型的​

​ChannelHandler​

​​,它們的功能很大程度上是由它們的超類決定。Netty以擴充卡(adapter)類的方式提供了許多預設的handler實作以簡化應用業務邏輯的開發。你已經知道在一個pipeline中每一個​

​ChannelHandler​

​都負責把事件傳遞給下一個handler。這些擴充卡類(以及它們的子類)會自動這麼做,是以你可以隻重寫你感興趣的方法和事件。

接下來我們來了解3個​

​ChannelHandler​

​的子類:encoders,decoders以及SimpleChannelInboundHandler(一個ChannelInboundHandlerAdapter的子類)。

encoder和decoder

當你通過Netty收發消息時,一個資料的轉換就會發生。傳入的消息會被解碼(decode),從位元組轉換成其他的格式,通常是一個Java對象。如果是傳出的消息,那麼就會把資料從目前的格式轉換為位元組。因為網絡資料總是位元組流的。

Netty為編碼解碼提供了大量與特定需求相關的抽象類。比如,你的應該可能會使用一個中間(intermediate)格式,不需要将消息馬上轉換為位元組。你仍然需要一個編碼器(encoder),但它可從不同的超類中繼承。為了決定使用哪一個,你可以應用一個簡單的命名約定(apply a simple naming convention)。

通常,基類會有像​

​ByteToMessageDecoder​

​​或​

​MessageToByteEncoder​

​​的類名。在具體的實作類中,有與​​Protobuf​​​相關的​

​ProtobufEncoder​

​​和​

​ProtobufDecoder​

​。

所有的編碼解碼擴充卡類都實作了​

​ChannelInboundHandler​

​​或​

​ChannelOutboundHandler​

​。

對于傳入資料來說,你會發現​

​channelRead​

​​方法或事件被重寫了。這個方法會被從傳入資料(inbound)Channel讀到的消息調用。然後會通過提供的​

​decoder​

​​調用​

​decode()​

​​方法然後将解碼了的位元組(解碼成Java對象)傳遞給下一個​

​ChannelInboundHandler​

​。

對于傳出資料來說就是相反的,一個​

​encoder​

​将消息轉換成位元組然後傳遞給一下handler。

SimpleChannelInboundHandler抽象類

通常你的應用需要使用一個handler來接收一個解碼了的資料然後應用一些業務邏輯。為了建立這樣隻有個handler,你隻需要繼承​

​SimpleChannelInboundHandler<T>​

​​,​

​T​

​就是你想要處理的資料的Java類型。

這個類中最重要的方法是​

​channelRead0(ChannelHandlerContext ctx,T t)​

​。它的實作完全取決于你,除非需要目前IO線程不被阻塞。

bootstrap

Nettyd的​

​bootstrap​

​類為配置一個應用網絡層的容器,這涉及到給一個特定的端口号綁定一個程式(process)(通常是用于啟動伺服器);或連接配接一個程式到另一個在特定的主機和端口号運作的程式(通常是用于啟動一個用戶端)。也就是監聽連接配接請求或建立連接配接。

嚴格的說術語連接配接指的是面向連接配接協定如TCP,它可以保證傳輸資料的有序性。

是以,有兩種​

​bootstrap​

​​:一種是為用戶端設計的,就叫​

​Bootstrap​

​​;另一種是為服務端設計的(​

​ServerBootstrap​

​)。

下表比較了這兩種類的差別:

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

我們重點解釋一下第二個不同,啟動一個用戶端隻需要一個​

​EventLoopGroup​

​,但服務端需要兩個(可以使同一個執行個體)。為啥子呢?

一個伺服器需要兩個不同的​

​Channel​

​​集合。第一個集合包含一個​

​ServerChannel​

​​代表伺服器自己綁定了本地端口的監聽socket。第二個集合會包含所有被建立來處理用戶端連接配接的​

​Channel​

​。下圖展示了這個模型。