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 之間的關系
這些關系是:
- 一個 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