Netty 框架學習 —— Netty 元件與設計

低吟不作語 發表於 2021-06-02

Channel、EventLoop 和 ChannelFuture

這一節將對 Channel、EventLoop 和 ChannelFuture 類進行討論,它們組合在一起,可以被認為是 Netty 網路抽象的代表:

  • Channel —— Socket
  • EventLoop —— 控制流、多執行緒處理、併發
  • CHannelFuture —— 非同步通知

1. Channel 介面

Netty 的 Channel 介面對應 Java 網路程式設計的 Socket,大大降低了直接使用 Socket 類的複雜性。此外,Channel 也擁有其他預定義的實現類:

  • EmbeddedChannel:測試 ChannelHandler
  • LocalServerChannel:用於同一個 JVM 內部實現 client 和 server 之間的通訊
  • NioSocketChannel:非同步的客戶端 TCP Socket 連線
  • NioServerSocketChannel:非同步的伺服器端 TCP Socket 連線
  • NioDatagramChannel:非同步的 UDP 連線
  • NioSctpChannel:非同步的客戶端 Sctp 連線
  • NioSctpServerChannel:非同步的 Sctp 伺服器端連線
  • OioSocketChannel:同步的客戶端 TCP Socket 連線
  • OioServerSocketChannel:同步的伺服器端 TCP Socket 連線
  • OioDatagramChannel:同步的 UDP 連線
  • OioSctpChannel:同步的 Sctp 伺服器端連線
  • OioSctpServerChannel:同步的客戶端 TCP Socket 連線

2. EventLoop 介面

EventLoop 用於處理連線的生命週期中所發生的事件,下圖說明了 Channel、EventLoop、Thread 以及 EventLoopGroup 之間的關係

Netty 框架學習 —— Netty 元件與設計

這些關係是:

  • 一個 EventLoopGroup 包含一個或多個 EventLoop
  • 一個 EventLoop 在它的生命週期內只和一個 Thread 繫結
  • 所有由 EventLoop 處理的 IO 事件都將在它專有的 Thread 上被處理
  • 一個 Channel 在它的生命週期內只註冊一個 EventLoop
  • 一個 EventLoop 可能會被分配到一個或多個 Channel

3. ChannelFuture 介面

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


ChannelHandler 和 ChannelPipeline

1. ChannelHandler 介面

ChannelHandler 可以看作是負責處理入站和出站資料的應用程式邏輯的容器,例如將資料從一個格式轉換為另一種格式,處理丟擲的異常等等。ChannelInboundHandler 是一個經常使用的子介面,這種型別的 ChannelHandler 接收入站事件和資料,這些資料隨後將被你的業務邏輯鎖處理。當你想要給客戶端傳送響應時,也可以從 ChannelInboundHandler 沖刷資料,通常應用程式的業務邏輯通常駐留在一個或者多個 ChannelInboundHandler 中

2. ChannelPipeline 介面

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

ChannelHandler 安裝到 ChannelPipeline 中的過程如下所示:

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

ChannelHandler 可以看作是處理往來 ChannelPipeline 事件(包括資料)的任何程式碼的通用容器,使事件流經過 ChannelPipeline 是 ChannelHandler 的工作,在應用程式的初始化或者引導階段被安裝。這些 ChannelHandler 接收事件、執行所實現的業務邏輯,並將資料傳遞給鏈中的下一個 ChannelHandler。它們的執行順序由它們被新增的順序所決定。實際上,ChannelPipeline 就是這些 ChannelHandler 的編排順序

當 ChannelHandler 被新增到 ChannelPipeline 時,它會被分配一個 ChannelHandlerContext,其代表了 ChannelHandler 和 ChannelPipeline 之間的繫結,雖然這個物件可以被用於獲取底層的 Channel,但它還是主要用於寫出站資料

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


編碼器和解碼器

當你通過 Netty 傳送或者接收一個訊息時,就會發生一次資料轉換。入站訊息會被解碼,即從位元組轉換成另一種格式,通常是一個 Java 物件。如果是出站訊息,則會發生相反方向的轉換,從當前格式被編碼為位元組。為此,Netty 為編碼器和解碼器提供了不同型別的抽象類,這些基類的名稱將類似於 ByteToMessageDecoder 或 MessageToByteEncoder。對於一些特殊型別,可能還會有 ProtobufEncoder 和 ProtobufDecoder 這樣的名稱,用來支援 Google 的 Protocol Buffers

使用 Netty 提供的編碼器/解碼器,你會發現對於入站資料來說,channelRead 方法/事件已經被重寫。對於每個從入站 Channel 讀取的訊息,將呼叫重寫後的 channelRead 方法。隨後,它將呼叫解碼器提供的 decode() 方法,將已解碼的位元組轉發給 ChannelPipeline 中的下一個 ChannelInboundHandler。出站訊息是反過來的,編碼器將訊息轉換為位元組,並將它們轉發給下一個 ChannelOutboundHandler


引導

Netty 的引導類為應用程式的網路層配置提供了容器,這涉及將一個程式繫結到某個指定的埠,或者將一個程式連線到另一個執行在某個指定主機的指定埠上的程式。通常我們把前面的用例稱為引導一個伺服器,後面的用例稱為引導一個客戶端。因此,有兩種型別的引導:一種用於客戶端(Bootstrap),而另一種(ServerBootstrap)用於伺服器

兩種型別的引導類區別如下:

  • ServerBootstrap 將繫結到一個埠,因為伺服器必須要監聽連線,而 Bootstrap 則是由想要連線到遠端節點的客戶端應用程式使用

  • 引導一個客戶端只需要一個 EventLoopGroup,但是一個 ServerBootstrap 則需要兩個。因為伺服器需要兩組不同的 Channel,第一組只包含一個 ServerChannel,代表伺服器自身已繫結到某個本地埠的正在監聽的套接字,而第二組將包含所有已建立的用來處理傳入客戶端連線的 Channel

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