閱讀957 返回首頁    go 汽車大全


Netty4詳解三:Netty架構設計

     讀完這一章,我們基本上可以了解到Netty所有重要的組件,對Netty有一個全麵的認識,這對下一步深入學習Netty是十分重要的,而學完這一章,我們其實已經可以用Netty解決一些常規的問題了。

一、先縱覽一下Netty,看看Netty都有哪些組件?


     為了更好的理解和進一步深入Netty,我們先總體認識一下Netty用到的組件及它們在整個Netty架構中是怎麼協調工作的。Netty應用中必不可少的組件:
  • Bootstrap or ServerBootstrap
  • EventLoop
  • EventLoopGroup
  • ChannelPipeline
  • Channel
  • Future or ChannelFuture
  • ChannelInitializer
  • ChannelHandler
     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>,其中T就是這個Handler處理的數據的類型(上一個Handler已經替我們解碼好了),消息到達這個Handler時,Netty會自動調用這個Handler中的channelRead0(ChannelHandlerContext,T)方法,T是傳遞過來的數據對象,在這個方法中我們便可以任意寫我們的業務邏輯了。

Netty從某方麵來說就是一套NIO框架,在Java NIO基礎上做了封裝,所以要想學好Netty我建議先理解好Java NIO,建議大家閱讀一下我的另兩篇文章:
Java NIO詳解(一) 
Java NIO詳解(二)


轉載請說明出處,原文鏈接:https://blog.csdn.net/suifeng3051/article/details/28861883 




最後更新:2017-04-03 08:26:26

  上一篇:go Notepad++ 格式化xml
  下一篇:go jqm選項卡開發,底部標簽式設計,jqm模仿iPhone手機桌麵菜單,jqm實戰開發,jqm開發例子Demo