這幾天在開發java server端代碼,用nio框架開發,目前比較好的有mina和netty,通過比較後來選擇了netty做爲咱們的開發框架。 學習netty最重要的資料就是官方文檔,我認爲這也是學習其餘框架共同的地方,主要都是英文的看起來可能比較累,在看的過程當中結合查詢到的中文資料效果會比較好。 netty原理篇: Netty是事件驅動的,能夠經過ChannelHandler鏈來控制執行流向。 ChannelHandler ChannelHandler鏈的執行過程是在 subReactor中同步的,因此若是業務處理handler耗時長,將嚴重影響可支持的併發數。 Netty的可擴展性很是好,而像ChannelHandler線程池化的須要,能夠經過在 ChannelPipeline中添加Netty內置的ChannelHandler實現類–ExecutionHandler實現,對使用者來講只是 添加一行代碼而已。對於ExecutionHandler須要的線程池模型,Netty提供了兩種可 選:1) MemoryAwareThreadPoolExecutor 可控制Executor中待處理任務的上限(超過上限時,後續進來的任務將被阻 塞),並可控制單個Channel待處理任務的上限;2) OrderedMemoryAwareThreadPoolExecutor 是 MemoryAwareThreadPoolExecutor 的子類,它還能夠保證同一Channel中處理的事件流的順序性,這主要是控制事件在異步處 理模式下可能出現的錯誤的事件順序,但它並不保證同一Channel中的事件都在一個線程中執行(一般也不必)。通常來 說,OrderedMemoryAwareThreadPoolExecutor 是個很不錯的選擇,固然,若是有須要,也能夠DIY一個。 ChannelPipeline p = Channels.pipeline(); /** * 採用默認ChannelPipeline管道 * 這意味着同一個DLPServerHandler實例將被多個Channel通道共享 * 這種方式對於DLPServerHandler中無有狀態的成員變量是能夠的,而且能夠提升性能!,沒有驗證。 */ ServerBootstrap serverBootstrap; ChannelPipeline pipeline=serverBootstrap.getPipeline(); 服務端程序一般的處理過程是:解碼請求數據、業務邏輯處理、編碼響應。從框架角度來講,能夠提供3個接口來控制並調度該處理過程;從更通用的角度來講,並不特化處理其中的每一步,而把每一步當作過濾器鏈中的一環,這也是Netty的作法。Netty對請求處理過程實現了過濾器鏈模式(ChannelPipeline),每一個過濾器實現了ChannelHandler接口。Netty中有兩種請求事件流類型也作了細分: 1)downstream event:其對應的ChannelHandler子接口是ChannelDownstreamHandler。downstream event是說從頭至尾執行ChannelPipeline中的ChannelDownstreamHandler,這一過程至關於向外發送數據的過程。 downstream event有:」write」、」bind」、」unbind」、 「connect」、 「disconnect」、」close」。 2)upstream event:其對應的ChannelHandler子接口是ChannelUpstreamHandler。upstream event處理的事件方向和downstream event相反,這一過程至關於接收處理外來請求的過程。upstream event有:」messageReceived」、 「exceptionCaught」、」channelOpen」、」channelClosed」、 「channelBound」、」channelUnbound」、 「channelConnected」、」writeComplete」、」channelDisconnected」、」channelInterestChanged」。 Netty中有個註釋
@interface ChannelPipelineCoverage,它表示被註釋的ChannelHandler是否能添加到多個ChannelPipeline中,其可選的值是」all」和」one」。」all」表示ChannelHandler是無狀態的,可被多個ChannelPipeline共享,而」one」表示ChannelHandler只做用於單個ChannelPipeline中。但ChannelPipelineCoverage只是個註釋而已,並無實際的檢查做用。對於ChannelHandler是」all」仍是」one」,仍是根據邏輯須要而定。好比,像解碼請求handler,由於可能解碼的數據不完整,須要等待下一次讀事件來了以後再繼續解析,因此解碼請求handler就須要是」one」的(不然多個Channel共享數據就亂了)。而像業務邏輯處理hanlder一般是」all」的。 (1)該upstream事件流中,首先通過MessageDecoder,其會將decode返回的解碼後的數據構形成 MessageEvent.getMessage(),因此在handler上下文關係中,MessageEvent.getMessage()並不必定都返回ChannelBuffer類型的數據。 (2)MessageServerHandler的e.getChannel().write(msg);操做將觸發DownstreamMessageEvent事件,也就是調用MessageEncoder將編碼的數據返回給客戶端。 建立了ChannelHandler,須要將他們註冊到ChannelPipeline,而ChannelPipeline又是和Channel對應的(是全局單例仍是每一個Channel對應一個ChannelPipeline實例依賴於實現)。能夠實現ChannelPipeline的工廠接口 ChannelPipelineFactory實現該目的。 核心類:buffer 該包核心的接口是ChannelBuffer和ChannelBufferFactory,下面予以簡要的介紹。 Netty使用ChannelBuffer來存儲並操做讀寫的網絡數據。ChannelBuffer除了提供和ByteBuffer相似的方法,還 提供了 一些實用方法,具體可參考其API文檔。 ChannelBuffer的實現類有多個,這裏列舉其中主要的幾個: 1)HeapChannelBuffer:這是Netty讀網絡數據時默認使用的ChannelBuffer,這裏的Heap就是Java堆的意 思,由於 讀SocketChannel的數據是要通過ByteBuffer的,而ByteBuffer實際操做的就是個byte數組,因此 ChannelBuffer的內部就包含了一個byte數組,使得ByteBuffer和ChannelBuffer之間的轉換是零拷貝方式。根據網絡字 節續的不一樣,HeapChannelBuffer又分爲BigEndianHeapChannelBuffer和 LittleEndianHeapChannelBuffer,默認使用的是BigEndianHeapChannelBuffer。Netty在讀網絡 數據時使用的就是HeapChannelBuffer,HeapChannelBuffer是個大小固定的buffer,爲了避免至於分配的Buffer的 大小不太合適,Netty在分配Buffer時會參考上次請求須要的大小。 2)DynamicChannelBuffer:相比於HeapChannelBuffer,DynamicChannelBuffer可動態自適 應大 小。對於在DecodeHandler中的寫數據操做,在數據大小未知的狀況下,一般使用DynamicChannelBuffer。 3)ByteBufferBackedChannelBuffer:這是directBuffer,直接封裝了ByteBuffer的 directBuffer。 對於讀寫網絡數據的buffer,分配策略有兩種:1)一般出於簡單考慮,直接分配固定大小的buffer,缺點是,對一些應用來講這個大小限制有 時是不 合理的,而且若是buffer的上限很大也會有內存上的浪費。2)針對固定大小的buffer缺點,就引入動態buffer,動態buffer之於固定 buffer至關於List之於Array。 buffer的寄存策略常見的也有兩種(實際上是我知道的就限於此):1)在多線程(線程池) 模型下,每一個線程維護本身的讀寫buffer,每次處理新的請求前清空buffer(或者在處理結束後清空),該請求的讀寫操做都須要在該線程中完成。 2)buffer和socket綁定而與線程無關。兩種方法的目的都是爲了重用buffer。 Netty對buffer的處理策略是:讀 請求數據時,Netty首先讀數據到新建立的固定大小的HeapChannelBuffer中,當HeapChannelBuffer滿或者沒有數據可讀 時,調用handler來處理數據,這一般首先觸發的是用戶自定義的DecodeHandler,由於handler對象是和ChannelSocket 綁定的,因此在DecodeHandler裏能夠設置ChannelBuffer成員,當解析數據包發現數據不完整時就終止這次處理流程,等下次讀事件觸 發時接着上次的數據繼續解析。就這個過程來講,和ChannelSocket綁定的DecodeHandler中的Buffer一般是動態的可重用 Buffer(DynamicChannelBuffer),而在NioWorker中讀ChannelSocket中的數據的buffer是臨時分配的 固定大小的HeapChannelBuffer,這個轉換過程是有個字節拷貝行爲的。 對ChannelBuffer的建立,Netty內部使用的是ChannelBufferFactory接口,具體的實現有 DirectChannelBufferFactory和HeapChannelBufferFactory。對於開發者建立 ChannelBuffer,可以使用實用類ChannelBuffers中的工廠方法。 Channel 從該結構圖也能夠看到,Channel主要提供的功能以下: 1)當前Channel的狀態信息,好比是打開仍是關閉等。 2)經過ChannelConfig能夠獲得的Channel配置信息。 3)Channel所支持的如read、write、bind、connect等IO操做。 4)獲得處理該Channel的ChannelPipeline,既而能夠調用其作和請求相關的IO操做。 在Channel實現方面,以一般使用的nio socket來講,Netty中的NioServerSocketChannel和NioSocketChannel分別封裝了java.nio中包含的 ServerSocketChannel和SocketChannel的功能。 Netty是事件驅動的,其經過ChannelEvent來肯定事件流的方向。一個ChannelEvent是依附於Channel的 ChannelPipeline來處理,並由ChannelPipeline調用ChannelHandler來作具體的處理。 對於使用者來講,在ChannelHandler實現類中會使用繼承於ChannelEvent的MessageEvent,調用其 getMessage()方法來得到讀到的ChannelBuffer或被轉化的對象。 Netty 在事件處理上,是經過ChannelPipeline來控制事件流,經過調用註冊其上的一系列ChannelHandler來處理事件,這也是典型的攔截 器模式。 在事件流的過濾器鏈 中,ChannelUpstreamHandler或ChannelDownstreamHandler既能夠終止流程,也能夠經過調用 ChannelHandlerContext.sendUpstream(ChannelEvent)或 ChannelHandlerContext.sendDownstream(ChannelEvent)將事件傳遞下去。 codec framework 對於請求協議的編碼解碼,固然是能夠按照協議格式本身操做ChannelBuffer中的字節數據。另外一方面,Netty也作了幾個很實用的 codec helper,這裏給出簡單的介紹。 1)FrameDecoder:FrameDecoder內部維護了一個 DynamicChannelBuffer成員來存儲接收到的數據,它就像個抽象模板,把整個解碼過程模板寫好了,其子類只需實現decode函數便可。 FrameDecoder的直接實現類有兩個:(1)DelimiterBasedFrameDecoder是基於分割符 (好比\r\n)的解碼器,可在構造函數中指定分割符。(2)LengthFieldBasedFrameDecoder是基於長度字段的解碼器。若是協 議 格式相似「內容長度」+內容、「固定頭」+「內容長度」+動態內容這樣的格式,就可使用該解碼器,其使用方法在API DOC上詳盡的解釋。 2)ReplayingDecoder: 它是FrameDecoder的一個變種子類,它相對於FrameDecoder是非阻塞解碼。也就是說,使用 FrameDecoder時須要考慮到讀到的數據有多是不完整的,而使用ReplayingDecoder就能夠假定讀到了所有的數據。 3)ObjectEncoder 和ObjectDecoder:編碼解碼序列化的Java對象。