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是傳遞過來的數據對象,在這個方法中我們便可以任意寫我們的業務邏輯了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM