1. 程式人生 > >Netty框架學習之(二):Netty元件簡介

Netty框架學習之(二):Netty元件簡介

1. 概覽

從高層次的角度來看Netty, 它主要為需要開發高效能應用的開發者解決了“技術”的和“體系結構”的問題。首先,它的基於 Java NIO 的非同步的和事件驅動的實現,保證了高負載下應用程式
效能的最大化和可伸縮性。其次, Netty 也包含了一組設計模式,將應用程式邏輯從網路層解耦,簡化了開發過程,同時也最大限度地提高了可測試性、模組化以及程式碼的可重用性。

為了可以更好的研究 Netty,本文主要對Netty的元件做一個簡單的描述,以及從高層次的角度來了解各個元件是如何協作的。

Netty中主要元件包括:
- Channel:代表了一個連結,與EventLoop一起用來參與IO處理。
- ChannelHandler:為了支援各種協議和處理資料的方式,便誕生了Handler元件。Handler主要用來處理各種事件,這裡的事件很廣泛,比如可以是連線、資料接收、異常、資料轉換等。
- ChannelPipeline:提供了 ChannelHandler 鏈的容器,並定義了用於在該鏈上傳播入站
和出站事件流的 API。
- EventLoop:Channel處理IO操作,一個EventLoop可以為多個Channel服務。
- EventLoopGroup:會包含多個EventLoop。

上述元件的關係結構如下圖所示:

這裡寫圖片描述

  1. 一個 EventLoopGroup 包含一個或者多個 EventLoop;
  2. 一個 EventLoop 在它的生命週期內只和一個 Thread 繫結;
  3. 所有由 EventLoop 處理的 I/O 事件都將在它專有的 Thread 上被處理;
  4. 一個 Channel 在它的生命週期內只註冊於一個 EventLoop;
  5. 一個 EventLoop 可能會被分配給一個或多個 Channel。

2. 元件說明

2.1 Channel

在Java中(Socket類),基本的 I/O 操作(bind()、 connect()、 read()和 write())依賴於底層網路傳輸所提供的功能。 Netty 的 Channel 接
口所提供的 API降低了直接使用 Socket 類的複雜性。Netty中也提供了使用多種方式連線的Channel:
- EmbeddedChannel;
- LocalServerChannel;
- NioDatagramChannel;
- NioSctpChannel;
- NioSocketChannel。

2.2 EventLoop&EventLoopGroup

EventLoop主要用於處理連線的生命週期中所發生的事件,它實現了Netty的執行緒模型部分的功能,執行緒模型指定了作業系統/程式語言/框架或者應用程式的上下文中的執行緒管理的方式。如何以及何時建立執行緒將對應用程式的效能產生顯著的影響。詳細細節將在後續的博文進行說明,本處只做簡單描述。

Channel&EventLoop&EventLoopGroup的協作方式大致如下:
這裡寫圖片描述

2.3 ChannelHandler&ChannelHandlerPipline

ChannelHandler

對於開發一個Netty的應用而言,主要開發的元件可能就是 ChannelHandler, 它充當了所有處理入站和出站資料的應用程式邏輯的容器,根據資料流向的不同,ChannelHandler可以分為ChannelInboundHandler以及ChannelOutboundHandler.

ChannelPipeline

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

ChannelHandler 安裝到 ChannelPipeline 中的過程如下所示:
1. 一個ChannelInitializer被註冊到了ServerBootstrap中;
2. 當 ChannelInitializer.initChannel()方法被呼叫時, ChannelInitializer
將在 ChannelPipeline 中安裝一組自定義的 ChannelHandler;
3. ChannelInitializer 將它自己從 ChannelPipeline 中移除

事件進入ChannelPipline時,會被定義的ChannelHandler順序的進行處理,如下圖所示:

這裡寫圖片描述

從一個客戶端應用程式的角度來看,如果事件的運動方向是從客戶端到伺服器端,那麼我們稱這些事件為Outbound的,反之則稱為Inbound的。需要注意的是對於inbound操作而言,處理的順序為從頭到尾,outbound的處理順序為從尾到頭。上圖中Inbound的處理順序為(1,2).Outbound的處理順序為(3,4).

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

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

ChannelHandler的類結構

首先要說明的是ChannelInboundHandle 和ChannelOutboundHandle 都繼承自ChannelHandler,將兩個類別的 ChannelHandler都混合新增到同一個 ChannelPipeline 中時,Netty 能區分 ChannelInboundHandler 實現和 ChannelOutboundHandler 實現,並確保資料只會在具有相同定向型別的兩個 ChannelHandler 之間傳遞。

對於ChannelHandler的實現類而言,可能不需要關注事件處理週期的每個環節,如果要把Inbound或是Outboud介面的每個方法都實現,就會額外的帶來很多的工作量,Netty對於該種情況提供了幾種Adapter的解決方案:

  • ChannelHandlerAdapter
  • ChannelInboundHandlerAdapter
  • ChannelOutboundHandlerAdapter
  • ChannelDuplexHandler

2.4 編碼器&解碼器

當通過 Netty 傳送或者接收一個訊息的時候,就將會發生一次資料轉換。入站訊息會被解碼:從位元組轉換為另一種格式,通常是一個 Java 物件。如果是出站訊息,則會發生
編碼:將從它的當前格式被編碼為位元組。

Netty預設已經提供了一堆的編/解碼器,一般需求已經可以滿足,如果不滿足可以通繼承ByteToMessageDecoder 或 MessageToByteEncoder來實現自己的編/解碼器。通過檢視類的繼承結構可以看出 Netty 提供的編碼器/解碼器介面卡類都實現了 ChannelOutboundHandler 或者 ChannelInboundHandler 介面。

對於解碼器Handler而言,其重寫了channelRead方法,對於每個從入站
Channel 讀取的訊息,這個方法都將會被呼叫。隨後,它將呼叫由解碼器所提供的 decode()方法,並將已解碼的位元組轉發給 ChannelPipeline 中的下一個 ChannelInboundHandler。OutboundHandler採用相反的處理方式。

2.5 SimpleChannelInboundHandler

你的應用程式會利用一個 ChannelHandler 來接收解碼訊息,並對該資料應用業務邏輯。 這種情況下,你只需要擴充套件基類 SimpleChannelInboundHandler,其中 T 是你要處理的訊息的 Java 型別。

在這種型別的 ChannelHandler 中, 最重要的方法是 channelRead0(ChannelHandlerContext,T)。除了要求不要阻塞當前的 I/O 執行緒之外,其具體實現完全取決於業務需要。

2.6 載入程式

Netty 的引導類為應用程式的網路層配置提供了容器,這涉及將一個程序繫結到某個指定的埠,或者將一個程序連線到另一個執行在某個指定主機的指定埠上的程序。

有兩種型別的引導:
1. 用於客戶端(Bootstrap)
2. 用於伺服器(ServerBootstrap)。

需要注意的是,引導一個客戶端只需要一個 EventLoopGroup,但是一個ServerBootstrap 則需要兩個(也可以是同一個例項)

下圖說明了Server端兩個EventLoopGroup的用途:

這裡寫圖片描述

第一個EventLoopGroup用來專門負責繫結到埠監聽連線事件,而把第二個EventLoopGroup用來處理每個接收到的連線。

對於Server端,如果僅由一個EventLoopGroup處理所有請求和連線的話,在併發量很大的情況下,這個EventLoopGroup有可能會忙於處理已經接收到的連線而不能及時處理新的連線請求,用兩個的話,會有專門的執行緒來處理連線請求,不會導致請求超時的情況,大大提高了併發處理能力