1. 程式人生 > >Netty4詳解三:Netty概要講解,全面瞭解Netty,做到胸有成竹

Netty4詳解三:Netty概要講解,全面瞭解Netty,做到胸有成竹

讀完這一章,我們基本上可以瞭解到Netty所有重要的元件,對Netty有一個全面的認識,這對下一步深入學習Netty是十分重要的,而學完這一章,我們其實已經可以用Netty解決一些常規的問題了。一、先縱覽一下Netty,看看Netty都有哪些元件? 為了更好的理解和進一步深入Netty,我們先總體認識一下Netty用到的元件及它們在整個Netty架構中是怎麼協調工作的。Netty應用中必不可少的元件:Bootstrap or ServerBootstrapEventLoopEventLoopGroupChannelPipelineChannelFuture or ChannelFutureChannelInitializerChannelHandler Bootstrap,一個Netty應用通常由一個Bootstrap開始,它主要作用是配置整個Netty程式,串聯起各個元件。 Handler,為了支援各種協議和處理資料的方式,便誕生了Handler元件。Handler主要用來處理各種事件,這裡的事件很廣泛,比如可以是連線、資料接收、異常、資料轉換等。 ChannelInboundHandler,一個最常用的Handler。這個Handler的作用就是處理接收到資料時的事件,也就是說,我們的業務邏輯一般就是寫在這個Handler裡面的,ChannelInboundHandler就是用來處理我們的核心業務邏輯。 ChannelInitializer,當一個連結建立時,我們需要知道怎麼來接收或者傳送資料,當然,我們有各種各樣的Handler實現來處理它,那麼ChannelInitializer便是用來配置這些Handler,它會提供一個ChannelPipeline,並把Handler加入到ChannelPipeline。 ChannelPipeline,一個Netty應用基於ChannelPipeline機制,這種機制需要依賴於EventLoop和EventLoopGroup,因為它們三個都和事件或者事件處理相關。 EventLoops的目的是為Channel處理IO操作,一個EventLoop可以為多個Channel服務。 EventLoopGroup會包含多個EventLoop。 Channel代表了一個Socket連結,或者其它和IO操作相關的元件,它和EventLoop一起用來參與IO處理。 Future,在Netty中所有的IO操作都是非同步的,因此,你不能立刻得知訊息是否被正確處理,但是我們可以過一會等它執行完成或者直接註冊一個監聽,具體的實現就是通過Future和ChannelFutures,他們可以註冊一個監聽,當操作執行成功或失敗時監聽會自動觸發。總之,所有的操作都會返回一個ChannelFuture。二、Netty是如何處理連線請求和業務邏輯的呢?-- Channels、Events 和 IO Netty是一個非阻塞的、事件驅動的、網路程式設計框架。當然,我們很容易理解Netty會用執行緒來處理IO事件,對於熟悉多執行緒程式設計的人來說,你或許會想到如何同步你的程式碼,但是Netty不需要我們考慮這些,具體是這樣: 一個Channel會對應一個EventLoop,而一個EventLoop會對應著一個執行緒,也就是說,僅有一個執行緒在負責一個Channel的IO操作。 關於這些名詞之間的關係,可以見下圖: 如圖所示:當一個連線到達,Netty會註冊一個channel,然後EventLoopGroup會分配一個EventLoop繫結到這個channel,在這個channel的整個生命週期過程中,都會由繫結的這個EventLoop來為它服務,而這個EventLoop就是一個執行緒。 說到這裡,那麼EventLoops和EventLoopGroups關係是如何的呢?我們前面說過一個EventLoopGroup包含多個Eventloop,但是我們看一下下面這幅圖,這幅圖是一個繼承樹,從這幅圖中我們可以看出,EventLoop其實繼承自EventloopGroup,也就是說,在某些情況下,我們可以把一個EventLoopGroup當做一個EventLoop來用。三、我們來看看如何配置一個Netty應用?-- BootsStrapping 我們利用BootsStrapping來配置netty 應用,它有兩種型別,一種用於Client端:BootsStrap,另一種用於Server端:ServerBootstrap,要想區別如何使用它們,你僅需要記住一個用在Client端,一個用在Server端。下面我們來詳細介紹一下這兩種型別的區別: 1.第一個最明顯的區別是,ServerBootstrap用於Server端,通過呼叫bind()方法來繫結到一個埠監聽連線;Bootstrap用於Client端,需要呼叫connect()方法來連線伺服器端,但我們也可以通過呼叫bind()方法返回的ChannelFuture中獲取Channel去connect伺服器端。 2.客戶端的Bootstrap一般用一個EventLoopGroup,而伺服器端的ServerBootstrap會用到兩個(這兩個也可以是同一個例項)。為何伺服器端要用到兩個EventLoopGroup呢?這麼設計有明顯的好處,如果一個ServerBootstrap有兩個EventLoopGroup,那麼就可以把第一個EventLoopGroup用來專門負責繫結到埠監聽連線事件,而把第二個EventLoopGroup用來處理每個接收到的連線,下面我們用一幅圖來展現一下這種模式: PS: 如果僅由一個EventLoopGroup處理所有請求和連線的話,在併發量很大的情況下,這個EventLoopGroup有可能會忙於處理已經接收到的連線而不能及時處理新的連線請求,用兩個的話,會有專門的執行緒來處理連線請求,不會導致請求超時的情況,大大提高了併發處理能力。 我們知道一個Channel需要由一個EventLoop來繫結,而且兩者一旦繫結就不會再改變。一般情況下一個EventLoopGroup中的EventLoop數量會少於Channel數量,那麼就很有可能出現一個多個Channel公用一個EventLoop的情況,這就意味著如果一個Channel中的EventLoop很忙的話,會影響到這個Eventloop對其它Channel的處理,這也就是為什麼我們不能阻塞EventLoop的原因。 當然,我們的Server也可以只用一個EventLoopGroup,由一個例項來處理連線請求和IO事件,請看下面這幅圖: 四、我們看看Netty是如何處理資料的?-- Netty核心ChannelHandler 下面我們來看一下netty中是怎樣處理資料的,回想一下我們前面講到的Handler,對了,就是它。說到Handler我們就不得不提ChannelPipeline,ChannelPipeline負責安排Handler的順序及其執行,下面我們就來詳細介紹一下他們: ChannelPipeline and handlers 我們的應用程式中用到的最多的應該就是ChannelHandler,我們可以這麼想象,資料在一個ChannelPipeline中流動,而ChannelHandler便是其中的一個個的小閥門,這些資料都會經過每一個ChannelHandler並且被它處理。這裡有一個公共介面ChannelHandler: 從上圖中我們可以看到,ChannelHandler有兩個子類ChannelInboundHandler和ChannelOutboundHandler,這兩個類對應了兩個資料流向,如果資料是從外部流入我們的應用程式,我們就看做是inbound,相反便是outbound。其實ChannelHandler和Servlet有些類似,一個ChannelHandler處理完接收到的資料會傳給下一個Handler,或者什麼不處理,直接傳遞給下一個。下面我們看一下ChannelPipeline是如何安排ChannelHandler的: 從上圖中我們可以看到,一個ChannelPipeline可以把兩種Handler(ChannelInboundHandler和ChannelOutboundHandler)混合在一起,當一個數據流進入ChannelPipeline時,它會從ChannelPipeline頭部開始傳給第一個ChannelInboundHandler,當第一個處理完後再傳給下一個,一直傳遞到管道的尾部。與之相對應的是,當資料被寫出時,它會從管道的尾部開始,先經過管道尾部的“最後”一個ChannelOutboundHandler,當它處理完成後會傳遞給前一個ChannelOutboundHandler。資料在各個Handler之間傳遞,這需要呼叫方法中傳遞的ChanneHandlerContext來操作, 在netty的API中提供了兩個基類分ChannelOutboundHandlerAdapter和ChannelOutboundHandlerAdapter,他們僅僅實現了呼叫ChanneHandlerContext來把訊息傳遞給下一個Handler,因為我們只關心處理資料,因此我們的程式中可以繼承這兩個基類來幫助我們做這些,而我們僅需實現處理資料的部分即可。 我們知道InboundHandler和OutboundHandler在ChannelPipeline中是混合在一起的,那麼它們如何區分彼此呢?其實很容易,因為它們各自實現的是不同的介面,對於inbound event,Netty會自動跳過OutboundHandler,相反若是outbound event,ChannelInboundHandler會被忽略掉。 當一個ChannelHandler被加入到ChannelPipeline中時,它便會獲得一個ChannelHandlerContext的引用,而ChannelHandlerContext可以用來讀寫Netty中的資料流。因此,現在可以有兩種方式來發送資料,一種是把資料直接寫入Channel,一種是把資料寫入ChannelHandlerContext,它們的區別是寫入Channel的話,資料流會從Channel的頭開始傳遞,而如果寫入ChannelHandlerContext的話,資料流會流入管道中的下一個Handler。 五、我們最關心的部分,如何處理我們的業務邏輯? -- Encoders, Decoders and Domain Logic Netty中會有很多Handler,具體是哪種Handler還要看它們繼承的是InboundAdapter還是OutboundAdapter。當然,Netty中還提供了一些列的Adapter來幫助我們簡化開發,我們知道在Channelpipeline中每一個Handler都負責把Event傳遞給下一個Handler,如果有了這些輔助Adapter,這些額外的工作都可自動完成,我們只需覆蓋實現我們真正關心的部分即可。此外,還有一些Adapter會提供一些額外的功能,比如編碼和解碼。那麼下面我們就來看一下其中的三種常用的ChannelHandler:Encoders和Decoders 因為我們在網路傳輸時只能傳輸位元組流,因此,才傳送資料之前,我們必須把我們的message型轉換為bytes,與之對應,我們在接收資料後,必須把接收到的bytes再轉換成message。我們把bytes to message這個過程稱作Decode(解碼成我們可以理解的),把message to bytes這個過程成為Encode。 Netty中提供了很多現成的編碼/解碼器,我們一般從他們的名字中便可知道他們的用途,如ByteToMessageDecoder、MessageToByteEncoder,如專門用來處理Google Protobuf協議的ProtobufEncoder、 ProtobufDecoder。 我們前面說過,具體是哪種Handler就要看它們繼承的是InboundAdapter還是OutboundAdapter,對於Decoders,很容易便可以知道它是繼承自ChannelInboundHandlerAdapter或 ChannelInboundHandler,因為解碼的意思是把ChannelPipeline傳入的bytes解碼成我們可以理解的message(即Java Object),而ChannelInboundHandler正是處理Inbound Event,而Inbound Event中傳入的正是位元組流。Decoder會覆蓋其中的“ChannelRead()”方法,在這個方法中來呼叫具體的decode方法解碼傳遞過來的位元組流,然後通過呼叫ChannelHandlerContext.fireChannelRead(decodedMessage)方法把編碼好的Message傳遞給下一個Handler。與之類似,Encoder就不必多少了。Domain Logic 其實我們最最關心的事情就是如何處理接收到的解碼後的資料,我們真正的業務邏輯便是處理接收到的資料。Netty提供了一個最常用的基類SimpleChannelInboundHandler,其中T就是這個Handler處理的資料的型別(上一個Handler已經替我們解碼好了),訊息到達這個Handler時,Netty會自動呼叫這個Handler中的channelRead0(ChannelHandlerContext,T)方法,T是傳遞過來的資料物件,在這個方法中我們便可以任意寫我們的業務邏輯了。

原文連結:http://blog.csdn.net/suifeng3051/article/details/28861883