Netty-鳥瞰
Bootstrap:Netty應用從構建一個Bootstrap開始,通過Bootstrap可以輕松的去配置并啟動應用。
ChannelHandler:為了能夠提供多協議并且多樣的去處理數據,Netty使用handler回調對象去處理特定的事件(包括正常的數據傳輸事件以及異常的處理事件)。通常我們可以實現ChannelInboundHandler,這樣我們可以把我們具體的業務邏輯處理封裝在這個我們實現的handler中。
ChannelInitializer:那我們怎么去綁定 ChannelHandler 去處理我們需要發送或者接收的消息呢?這里就用到ChannelInitializer,它的指責就是將 ChannelHandler 的實現加入到 ChannelPipeline。(事實上ChannelInitializer本身就是一個ChannelHandler,只不過這個handler會在加入其他handler的同時將自己從ChannelPipeline中移除)
ChannelPipeline: ChannelPipeline 和 EventLoop、EventLoopGroup相近都與事件和事件處理相關。
EventLoop & EventLoopGroup:指責在于處理通道中的IO操作,單個的 EventLoop 通常會處理多個通道上的事件。而 EventLoopGroup 包含了了多個 EventLoop ,并能用于去獲取 EventLoop。
Channel:一個通道代表了一個 socket 鏈接,或者能夠進行IO處理的組件,因此這里用EventLoop來管理。
-
ChannelFuture: Netty中的IO操作都是異步的(包括連接、讀、寫),這就意味著我們并不能知道操作是執行成功是否返回,但是我們需要在后續的操作中執行檢測或者注冊一些監聽器來獲取通知。Netty使用 Futures 和 ChannelFutures 去注冊監聽來獲取通知。
ChannelFuture是一個特殊的 java.util.concurrent.Future,它允許我們注冊 ChannnelFutureListeners 到ChannelFuture。這些listener會在操作執行完成時得到通知。本質上來說,ChannelFuture是操作執行結果的占位符。所有的操作都會返回一個 ChannelFuture。
EventLoop
Netty 是一個非阻塞的,事件驅動的網絡框架。初看,Netty是用多線程來處理IO事件的。接觸過多線程編程的人可能會想,在這樣需要同步我們的代碼。但事實上,Netty的設計使我們不需要做過多的這些考慮。
如圖中所示,Netty使用 EventLoopGroup 的組件里面有一個或者多個 EventLoop。當一個通道(Channel)被注冊進來,Netty會綁定這個通道到一個單獨的 EventLoop (當然也是在一個單獨的線程中),并且這個通道的生命周期只會與這一個 EventLoop 綁定。這也就是為什么在我們的應用在Netty框架下不需要做同步處理(所有的IO操作都是在給定的通道及同一個線程中)
EventLoop 總是被綁定到一個單獨的線程中,在其生命周期中絕不會更換線程。
如圖:EventLoop 和 EventLoopGroup 是一種 "is-a"關系
一個 EventLoop 就是一個 EventLoopGroup,這也就意味著我們在傳入一個 EventLoopGroup 的地方同樣也能指定一個 EventLoop。
BootStrap & ServeBootStrap
BootStrap:用于創建客戶端;
ServerBootStrap:用于創建服務端;
不同點一:
ServerBootStrap 綁定到一個端口去監聽客戶端的鏈接;BootStrap 通常調用 connect() / bind(),然后在稍后使用 Channel (包含在ChannelFuture中)來進行連接。
不同點二:
客戶端 BootStrap 使用一個單獨的EventLoopGroup;然而,ServerBootStrap 使用兩個 EventLoopGroup (事實上使用同一個也是可以的),第一個集合包含一個單獨的 ServerChannel 代表服務端自己的socket(這個socket被綁定到本地的一個端口上了),第二個集合包含所有的服務端接收的鏈接通道。
如圖,EventLoopGroupA 唯一的目的是接收鏈接然后將它們交付到 EventLoopGroupB。
Netty這樣做的根本目的是為了客服鏈接瓶頸。在一個高并發的場景下,可能會有極其多的鏈接接入,當只有一個Group時,處理已有鏈接已經很繁忙,以至于無法接收新的鏈接,這最終會導致很多鏈接會超時。而使用兩個Group,接收鏈接和處理鏈接分開,這樣所有的鏈接都可以被接收。
EventLoopGroup 可能包含多個EventLoop(不過也取決與我們的具體配置),每一個通道會有一個 EventLoop 與它綁定并且在整個生命周期內都不會更換。不過,由于 EventLoopGroup 中的 EventLoop 會比通道小,所以會有很多通道共享一個 EventLoop,這也意味著在同一個 EventLoop 中,一個通道處理繁忙的話,將不允許去處理其他的通道,因此不要使用阻塞EventLoop的原因。
如圖,當只有一個group時,同一個實例會被使用兩次。
ChannelHandler
我們很容易想到 ChannelHandler 是用來處理數據流的,但是實際上 ChannelHandler 還能有很多其他的應用。
如圖,從類繼承關系上可以看出,我們有兩種 ChannelHandler,也反映出數據流是雙向的(數據可以從我們的應用向外流出,也能從遠端流入我們的應用)。
數據從一段流到另一端的過程中,會經過一個或者多個 ChannelHandler 的處理。這個 ChannelHandler 會被加入到應用中,并且它們加入的順序決定了它們處理數據的順序。
既然會設計到多個 ChannelHandler 協作,必然會有一定的規則需要遵守。這里的規則很簡單:ChannelPipeline 就是這寫 ChannelHandler 的約束。每一個 ChannelHandler 處理完自己的部分后都會將數據傳遞到同一個 ChannelPipeline 中的下一個 ChannelHandler,直到沒有 ChannelHandler 為止。
如圖:反映了 ChannelInboundHandler 和 ChannelOutboundHandler 能夠同時存在于一個 ChannelPipeline 中。
由于我們的 ChannelHandler 通常實現自 ChannelInboundHandler 或 ChannelOutboundHandler 所以Netty會知道各個handler的類型,這樣在一個流出的事件中就可以跳過所有的 ChannelInboundHandler。
每一個加入 ChannelPipeline 中的 ChannelHandler 會得到一個 ChannelHandlerContext。通常獲得 ChannelHandlerContext 的引用是安全的,但是在 UDP 協議下可能不一定。 這個 ChannelHandlerContext 可以用于獲取底層的 channel 用于 write/send 消息。這樣就存在兩種方式來發送消息:直接寫到通道 或者 通過 ChannelHandlerContext 來寫消息,它們的主要區別是,直接寫到通道中的消息會從 ChannelPipeline 的尾部開始,寫到 ChannelHandlerContext 中的消息會傳遞給下一個handler
通過回調方法中攜帶的 ChannelHandlerContext 參數,我們可以將一個事件可以定向到下一個 ChannelInboundHandler 或者 前一個 ChannelOutboundHandler 中。(Netty為我們提供的抽象基類 ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 只提供單方向的傳遞,但是我們不需要手動調用傳遞方法)
Encoder & Decoder
每一個通道都有傳遞Netty事件的職責,Netty類中 *Adapter 結尾的類幫我們實現了這一過程,這樣我們不需要去關注這部分的工作,我們只需要去處理我們感興趣的部分。除了 *Adapter 的類外,同樣還有很多其他功能擴展的類我們可以使用,比如 encode/decode 消息。
當我們接收到消息時,我們必須將其從 bytes 轉化成 Java對象。當發送消息時,我們同樣需要將消息從Java對象轉換成bytes。這樣的操作很頻繁,因此Netty為我們提供了很多基礎類,類似于 ByteToMessageDecoder 和 MessageToByteEncoder 就提供這樣的功能。我們應用中用的最多的可能是讀取消息并解碼然后再進行一系列的其他處理,我們可以繼承 SimpleChannelInboundHandler<T> (T 就是我們要處理的消息類型),這個handler的主要方法channelRead0(ChannelHandlerContext,T),不能何時調用該方法,T 對象就是我們要處理的消息。
在IO線程中,不能進行阻塞的操作。Netty 允許在添加 ChannelHandler 到 ChannelPipeline 中時指定一個 EventExecutorGroup, 它會被用于獲取一個 EventExecutor 對象,這個 EventExecutor 將用于執行所有的ChannelHandler的操作(EventExecutor 會使用一個另外的線程)