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
注意在這種設計中,給定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
到pipeline中。ChannelHandlers
-
把它自己從ChannelInitializer
ChannelPipeline
中移除掉
具有廣泛的用途,你能把它想成一個為任何處理在ChannelHandler
中流通(傳入傳出)的事件(包括資料)的代碼的通用容器(generic container)。下圖說明了ChannelPiple
和ChannelInboundHandler
繼承自ChannelOutboundHandler
的關系。ChannelHandler
事件通過在初始化或啟動應用期間加載的
ChannelHandler
在pipeline中傳遞,
ChannelHandler
接受事件并執行在其中實作了的業務邏輯,并将資料傳遞給(handler)鍊中的下一個handler。這些
handler
根據它們被添加的順序來執行。
上圖展示了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
。下圖展示了這個模型。