Channel、EventLoop和ChannelFuture
- Channel——Socket;
- EventLoop——控制流、多線程處理、并發
- ChannelFuture異步通知
Channel接口
基于I/O操作(例如:bind()、connect()、read()和write())依賴于底層網絡傳輸提供的原語。在基于Java的網絡編程中,其基本構造為類Socket。
Netty的Channel接口所提供的API,大大降低了直接使用Socket類的復雜性。
Channel擁有許多預定義的、專門化實現的廣泛類層次結構的根,如下:
- EmbeddedChannel
- LocalServerChannel
- NioDatagramChannel
- NioSctpChannel
- NioSocketChannel
EventLoop接口
EventLoop定義了Netty的核心抽象,用于處理連接的生命周期中所發生的事件。
<center>Channel、EventLoop、Thread、EventLoopGroup關系示意圖</center>
- 一個EventLoopGroup包含一個或者多個EventLoop
- 一個EventLoop在它的生命周期內只和一個Thread綁定
- 所有由EventLoop處理的I/O事件都將在它專有的Thread上被處理
- 一個Channel在它的生命周期內只注冊于一個EventLoop
- 一個EventLoop可能會被分配一個或多個Channel
一個給定Channel的I/O操作都是由相同的Thread執行的,實際上消除了對于同步的需要
ChanneFuture接口
Netty中所有的I/O操作都是異步的,因為一個操作不可能立即返回,所以我們需要一種用于在之后的某個時間點確定其結果的方法。
因此Netty提供了ChannelFuture接口,其addListener()方法注冊了一個ChannelFutureListener,以便在某個操作完成時(無論是否成功)得到通知。
ChannelHandler和ChannelPipeline
主要用來管理數據流已經執行應用程序處理邏輯
ChannelHandler接口
ChannelHandler充當了所有處理入站和出站數據的應用程序邏輯的容器。
ChannelHandler可專門用于幾乎任何類型的動作,例如將數據從一種格式轉換為另外一種格式,或者處理轉換過程中所拋出的異常。
ChannelHandler可以用來接收入站事件和數據,隨后使用應用程序的業務邏輯進行處理。當你的客戶端需要發送響應時,可以從ChannelInboundhandler沖刷數據。
你的應用程序的業務邏輯通常駐留在一個或者多個ChannelInboundHandler中。
ChannelPipeline
ChannelPipeline是ChannelHandler鏈的容器,并定義用于在該鏈上傳播入站和出站事件流的API。當Channel被創建時,它會被自動地分配到它專屬的ChannelPipeline。
ChannelHandler被安裝到ChannelPipeline中過程如下:
- 一個ChannelInitializer的實現被注冊到了ServerBootstrap中
- 當ChannelInitializer.initChannel()方法被調用時,ChannelInitalizer將在ChannelPipeline中安裝一組自定義的ChannelHandler
- ChannelInitializer將它自己從ChannelPipeline中移除
?ChannelHandler可以讓事件流經ChannelPipeline,它們是在應用程序的初始化或者引導階段被安裝的。這些對象接收事件、執行它們所實現的處理邏輯,并將數據傳遞給鏈中的下一個ChannelHandler。它們的執行順序是由它們被添加的順序決定的。
ChannelPipeline是這些ChannelHandler的編排順序。
入站和出站ChannelHandler可以被安裝到同一個ChannelPipeline中。如果一個消息或者任何其他的入站事件被讀取,那么它會從ChannelPipeline的頭部開始流動,并被傳遞給第一個ChannelInboundHandler。這個ChannelHandler不一定會實際地修改數據,具體取決于它的具體功能,在這之后,數據將會被傳遞給鏈中的下一個ChannelInboundHandler。最終,數據會到達ChannelPipeline的尾端,屆時,所有處理就都結束了。
出站數據將會從ChannelOutboundHandler鏈的尾端開始流動,直到它到達鏈的頭部為止。在這之后,出站數據將會到達網絡傳輸層。
通過使用作為參數傳遞到每個方法的ChannelHandlerContext,事件可以被傳遞給當前ChannelPipeLine中的下一個ChannelHandler。因為用戶并不是關心所有的事件,因此Netty提供了抽象類ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter。通過調用ChannelHandlerContext上的對應的方法,都可以簡單地將事件傳遞給下一個ChannelHandler的方法的實現。
當ChannelHandler被添加到ChannelPipeline時,它將會被分配一個ChannelHandlerContext,它代表了Channel和ChannelPipeline之間的綁定。雖然這個對象可以被用于獲取底層的Channel,但是它主要還是被用于出站寫數據。
在Netty中,有兩種發送消息的方式:
- 直接寫到Channel中,會導致消息從ChannelPipeline的尾端開始流動
- 寫到和ChannelHandler相關聯的ChannelHandlerContext對象中,會導致消息從ChannelPipeline中的下一個ChannelHandler開始流動
如果將兩個類別(Inboud和Outbound)的ChannelHandler都混合添加到一個ChannelPipeline會發生什么?
雖然ChannelInboundHandler和ChannelOutboundHandler都擴展自ChannelHandler,但是Netty可以區分兩種Handler的實現,并確保數據只會在具有相同定向類型的兩個ChannelHandler之間傳遞。
深入了解ChannelHandler
不同類型的ChannelHandler各自的功能主要取決于它們的超類。Netty以適配器類的形式提供了大量默認的ChannelHandler實現,其旨在簡化應用程序處理邏輯的開發過程。
這些適配器類可以自動將事件轉發到ChannelPipeline中的下一個ChannelHandler,所以你可以只重寫那些你想要特殊處理的方法和事件。
為什么需要適配器?
有一些適配器類可以將編寫自定義的ChannelHanlder所需要的努力降到最低限度,因為它們提供了定義在對應接口中的所有方法的默認實現。
常用的適配器類有:
- ChannelHandlerAdapter
- ChannelInboundHandlerAdapter
- ChannelOutboundHandlerAdapter
- ChannelDuplexHandler
編碼解碼器
所有由Netty提供的編碼/解碼適配器類都實現了ChannelOutboundHandler或者ChannelInboundHandler接口。
對于入站數據,channelRead方法已經被重寫了。對于每一個從入站Channel讀取的消息,這個方法都會被調用。隨后,它將調用由預置解碼器提供的decode()方法,并將已解碼的字節轉發給ChannelPipeline中的下一個ChannelHandler,出站相反。
抽象類SimpleChannelInboundHandler
最常見的一個情況,你的應用程序會利用一個ChannelHandler來接受解碼消息,并對該數據應用業務邏輯。要創建一個這樣的ChannelHandler,只需要擴展SimpleChannelInboundHandler<T>,其中<T>是你要處理的消息的Java類型。
在這個ChannelHandler中,你將需要重寫基類的一個或者多個方法,并且獲取一個ChannelHandlerContext的引用,這個引用將作為參數傳遞給ChannelHandler的所有方法。
在這種類型的ChannelHandler中,最重要的方法是channelRead0(ChannelHandlerContext , T)。除了要求不阻塞當前的I/O線程之外,其具體實現完全取決于你。
引導
Netty的引導類為應用程序的網絡層配置提供了容器。
- 用于客戶端(Bootstrap)引導,將一個進程連接到另一個運行在某個指定主機的指定端口上的進程。
- 用于服務器(ServerBootstrap)引導,將一個進程綁定到某個指定的端口
<center>Bootstrap類比較</center>
類別 | Bootstrap | ServerBootstrap |
---|---|---|
網絡編程中的作用 | 連接到遠程主機和端口 | 綁定到一個本地端口 |
EventLoopGroup的數目 | 1 | 2 |
區別分析:
- ServerBootstrap將綁定到一個端口,因為服務器必須要監聽連接,而Bootstrap則是由想要連接到遠程節點的客戶端應用程序所使用的。
- 為什么服務端需要兩個EventLoopGroup(可以是同一個實例)?因為服務器需要兩組不同的Channel。第一組將只包含一個ServerChannel,代表服務器自身的已綁定到某個本地端口第二次正在監聽的套接字。第二組包含所有已經創建的用來處理傳入客戶端連接(對于每個服務器已經接受的連接都一個)的Channel。
3.與ServerChannel相關聯的EventLoopGroup將分配一個負責為傳入連接請求創建Channel的EventLoop。一旦連接被接受,第二個EventLoopGroup就會給它的Channel分配一個EventLoop。
<center>具有兩個EventLoopGroup的服務器</center>