第三章:Netty核心概念

這一章咱們將討論Netty的10個核心類。編程

Bootstrapbootstrap

ServerBootstrap服務器

EventLoop網絡

EventLoopGroup多線程

ChannelPipelineapp

Channel異步

Futuresocket

ChannelFutureoop

ChannelInitializer性能

ChannelHandler

1.Crash Course(速成課程)

一個Netty程序開始於Bootstrap類,Bootstrap類是Netty提供的一個能夠經過簡單配置來設置或"引導"程序的一個很重要的類。

Netty中設計了Handlers來處理特定的"event"和設置Netty中的事件,從而來處理多個協議和數據。

事件能夠描述成一個很是通用的方法,由於你能夠自定義一個handler,用來將Object轉成byte[]或將byte[]轉成Object;也能夠定義個handler處理拋出的異常。

你會常常編寫一個實現ChannelInboundHandler的類,ChannelInboundHandler是用來接收消息,當有消息過來時,你能夠決定如何處理。當程序須要返回消息時能夠在ChannelInboundHandler裏write/flush數據。能夠認爲應用程序的業務邏輯都是在ChannelInboundHandler中來處理的,業務羅的生命週期在ChannelInboundHandler中。

Netty鏈接客戶端或綁定服務器須要知道如何發送或接收消息,這是經過不一樣類型的handlers來作的,多個Handlers是怎麼配置的?Netty提供了ChannelInitializer類用來配置Handlers。

ChannelInitializer是經過ChannelPipeline來添加ChannelHandler的,如發送和接收消息,這些Handlers將肯定發的是什麼消息。ChannelInitializer自身也是一個ChannelHandler,在添加完其餘的handlers以後會自動從ChannelPipeline中刪除本身。

全部的Netty程序都是基於ChannelPipeline。ChannelPipeline和EventLoop和EventLoopGroup密切相關,由於它們三個都和事件處理相關,因此這就是爲何它們處理IO的工做由EventLoop管理的緣由。

Netty中全部的IO操做都是異步執行的,例如你鏈接一個主機默認是異步完成的;寫入/發送消息也是一樣是異步。也就是說操做不會直接執行,而是會等一會執行,由於你不知道返回的操做結果是成功仍是失敗,可是須要有檢查是否成功的方法或者是註冊監聽來通知,Netty使用Futures和ChannelFutures來達到這種目的

Future註冊一個監聽,當操做成功或失敗時會通知。ChannelFuture封裝的是一個操做的相關信息,操做被執行時會馬上返回ChannelFuture。

2 Channels,Events and Input/Output(IO)

Netty是使用多線程處理IO,對於多線程編程讀者可能須要同步,這樣會影響程序性能,Netty的設置保證不會用到同步。

下圖顯示一個EventLoopGroup和一個Channel關聯一個單一的EventLoop,Netty中的EventLoopGroup包含一個或多個EventLoop,而EventLoop就是一個Channel執行實際工做的線程。EventLoop老是綁定一個單一的線程,在其生命週期內不會改變。

當註冊一個Channel後,Netty將這個Channel綁定到一個EventLoop,在Channel的生命週期內老是被綁定到一個EventLoop。在Netty IO操做中,你的程序不須要同步,由於一個指定通道的全部IO始終由同一個線程來執行。

EventLoop和EventLoopGroup的關聯不是直觀的,由於咱們說過EventLoopGroup包含一個或多個EventLoop,可是上面的圖顯示EventLoop是一個EventLoopGroup,這意味着你能夠只使用一個特定的EventLoop。

3 什麼是Bootstrap?爲何使用它?

引導」是Netty中配置程序的過程,當你須要鏈接客戶端或服務器綁定指定端口時須要使用bootstrap。

引導」有兩種類型,一種是用於客戶端的Bootstrap(也適用於DatagramChannel),一種是用於服務端的ServerBootstrap。

Bootstrap和ServerBootstrap之間的差別:

1.Bootstrap用來鏈接遠程主機,有1個EventLoopGroup

2.ServerBootstrap用來綁定本地端口,有2個EventLoopGroup

第一個差別很明顯,「ServerBootstrap」監聽在服務器,監聽一個端口輪詢客戶端的「Bootstrap」或DatagramChannel是否鏈接服務器。一般須要調用「Bootstrap」類的connect()方法,可是也能夠先調用bind()再調用connect()進行鏈接,以後使用的Channel包含在bind()返回的ChannelFuture中。

第二個差異也許是最重要的。客戶端bootstraps/applications使用一個單例EventLoopGroup,而ServerBootstrap使用2個EventLoopGroup(實際上使用的是相同的實例),它可能不是顯而易見的,可是它是個好的方案。一個ServerBootstrap能夠認爲有2個channels組,第一組包含一個單例ServerChannel,表明持有一個綁定了本地端口的socket;第二組包含全部的Channel,表明服務器已接受了的鏈接。下圖形象的描述了這種狀況:

EventLoopGroup A惟一的目的就是接受鏈接而後交給EventLoopGroup B。

Netty可使用兩個不一樣的Group,由於服務器程序須要接受不少客戶端鏈接的狀況下,一個EventLoopGroup將是程序性能的瓶頸,由於事件循環忙於處理鏈接請求,沒有多餘的資源和空閒來處理業務邏輯,最後的結果會是不少鏈接請求超時。如有兩EventLoops, 即便在高負載下,全部的鏈接也都會被接受,由於EventLoops接受鏈接不會和哪些已經鏈接了的處理共享資源。

EventLoopGroup和EventLoop是什麼關係?

EventLoopGroup能夠包含不少個EventLoop,每一個Channel綁定一個EventLoop不會被改變,由於EventLoopGroup包含少許的EventLoop的Channels,不少Channel會共享同一個EventLoop。這意味着在一個Channel保持EventLoop繁忙會禁止其餘Channel綁定到相同的EventLoop。咱們能夠理解爲EventLoop是一個事件循環線程,而EventLoopGroup是一個事件循環集合。

 若是你決定兩次使用相同的EventLoopGroup實例配置Netty服務器,下圖顯示了它是如何改變的:

Netty容許處理IO和接受鏈接使用同一個EventLoopGroup,這在實際中適用於多種應用。上圖顯示了一個EventLoopGroup處理鏈接請求和IO操做。

4 Channel Handlers and Data Flow(通道處理和數據流)

本節咱們一塊兒來看看當你發送或接收數據時發生了什麼?要明白Netty程序wirte或read時發生了什麼,首先要對Handler是什麼有必定的瞭解。

Handlers自身依賴於ChannelPipeline來決定它們執行的順序,所以不可能經過ChannelPipeline定義處理程序的某些方面,反過來不可能定義也不可能經過ChannelHandler定義ChannelPipeline的某些方面。不必說咱們必須定義一個本身和其餘的規定。本節將介紹ChannelHandler和ChannelPipeline在某種程度上細微的依賴。

Netty的ChannelHandler是你的應用程序中處理最多的。ChannelHandler到底是什麼?ChannelHandler是一段執行業務邏輯處理數據的代碼,它們來來每每的經過ChannelPipeline。ChannelHandler是定義一個handler的父接口,ChannelInboundHandler和ChannelOutboundHandler都實現ChannelHandler接口

Netty中有兩個方向的數據流,上圖顯示的入站(ChannelInboundHandler)和出站(ChannelOutboundHandler)之間有一個明顯的區別:若數據是從用戶應用程序到遠程主機則是「出站(outbound)」,相反若數據時從遠程主機到用戶應用程序則是「入站(inbound)」。

爲了使數據從一端到達另外一端,一個或多個ChannelHandler將以某種方式操做數據。這些ChannelHandler會在程序的「引導」階段被添加ChannelPipeline中,而且被添加的順序將決定處理數據的順序。ChannelPipeline的做用咱們能夠理解爲用來管理ChannelHandler的一個容器,每一個ChannelHandler處理各自的數據(例如入站數據只能由ChannelInboundHandler處理),處理完成後將轉換的數據放到ChannelPipeline中交給下一個ChannelHandler繼續處理,直到最後一個ChannelHandler處理完成。

下圖顯示了ChannelPipeline的處理過程:

上圖顯示ChannelInboundHandler和ChannelOutboundHandler都要通過相同的ChannelPipeline。

在ChannelPipeline中,若是消息被讀取或有任何其餘的入站事件,消息將從ChannelPipeline的頭部開始傳遞給第一個ChannelInboundHandler,這個ChannelInboundHandler能夠處理該消息或將消息傳遞到下一個ChannelInboundHandler中,一旦在ChannelPipeline中沒有剩餘的ChannelInboundHandler後,ChannelPipeline就知道消息已被全部的餓Handler處理完成了。

任何出站事件或寫入將從ChannelPipeline的尾部開始,並傳遞到最後一個ChannelOutboundHandler。ChannelOutboundHandler的做用和ChannelInboundHandler相同,它能夠傳遞事件消息到下一個Handler或者本身處理消息。不一樣的是ChannelOutboundHandler是從ChannelPipeline的尾部開始,而ChannelInboundHandler是從ChannelPipeline的頭部開始,當處理完第一個ChannelOutboundHandler處理完成後會出發一些操做,好比一個寫操做。

一個事件能傳遞到下一個ChannelInboundHandler或上一個ChannelOutboundHandler,在ChannelPipeline中經過使用ChannelHandlerContext調用每個方法。

Netty提供了抽象的事件基類稱爲ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter。

每一個都提供了在ChannelPipeline中經過調用相應的方法將事件傳遞給下一個Handler的方法的實現。咱們能覆蓋的方法就是咱們須要作的處理。

出站和入站的操做不一樣,能放在同一個ChannelPipeline工做?

入站和出站Handler有不一樣的實現,Netty能跳過一個不能處理的操做,因此在出站事件的狀況下,ChannelInboundHandler將被跳過,Netty知道每一個handler都必須實現ChannelInboundHandler或ChannelOutboundHandler。

當一個ChannelHandler添加到ChannelPipeline中時得到一個ChannelHandlerContext。

Netty中發送消息有兩種方法:直接寫入通道或寫入ChannelHandlerContext對象。這兩種方法的主要區別以下:

    a. 直接寫入通道致使處理消息從ChannelPipeline的尾部開始

    b. 寫入ChannelHandlerContext對象致使處理消息從ChannelPipeline的下一個handler開始

5 編碼器、解碼器和業務邏輯:細看Handlers

Netty中有不少不一樣類型的handlers,每一個handler的依賴於它們的基類。Netty提供了一系列的「Adapter」類,每一個handler負責轉發事件到ChannelPipeline的下一個handler。

在*Adapter類(和子類)中是自動完成的,所以咱們只須要在感興趣的*Adapter中重寫方法。這些功能能夠幫助咱們很是簡單的編碼/解碼消息。

有幾個適配器(adapter)容許自定義ChannelHandler,通常自定義ChannelHandler須要繼承編碼/解碼適配器類中的一個。Netty有如下適配器:

ChannelHandlerAdapter

ChannelInboundHandlerAdapter

ChannelOutboundHandlerAdapter

三個ChannelHandler中,咱們重點看看encoders,decoders和SimpleChannelInboundHandler<I>,SimpleChannelInboundHandler<I>繼承ChannelInboundHandlerAdapter。

5.1 Encoders(編碼器), decoders(解碼器)

發送或接收消息後,Netty必須將消息數據從一種形式轉化爲另外一種。接收消息後,須要將消息從字節碼轉成Java對象(由某種解碼器解碼);發送消息前,須要將Java對象轉成字節(由某些類型的編碼器進行編碼)。這種轉換通常發生在網絡程序中,由於網絡上只能傳輸字節數據。

有多種基礎類型的編碼器和解碼器,要使用哪一種取決於想實現的功能。要弄清楚某種類型的編解碼器,從類名就能夠看出如「ByteToMessageDecoder」、「MessageToByteEncoder」,還有Google的協議「ProtobufEncoder」和「ProtobufDecoder」。

若是是解碼器則有一個ChannelInboundHandlerAdapter或ChannelInboundHandler,全部的解碼器都繼承或實現它們。「channelRead」方法/事件被覆蓋,這個方法從入站(inbound)通道讀取每一個消息。重寫的channelRead方法將調用每一個解碼器的「decode」方法並經過ChannelHandlerContext.fireChannelRead(Object msg)傳遞給ChannelPipeline中的下一個ChannelInboundHandler。

相似入站消息,當你發送一個消息出去(出站)時,除編碼器將消息轉成字節碼外還會轉發到下一個ChannelOutboundHandler。

5.2 業務邏輯(Domain logic)

也許最多見的是應用程序處理接收到消息後進行解碼,而後供相關業務邏輯模塊使用。因此應用程序只須要擴展SimpleChannelInboundHandler<I>,也就是咱們自定義一個繼承SimpleChannelInboundHandler<I>的handler類,其中<I>是handler能夠處理的消息類型。經過重寫父類的方法能夠得到一個ChannelHandlerContext的引用,它們接受一個ChannelHandlerContext的參數,你能夠在class中當一個屬性存儲。

處理程序關注的主要方法是「channelRead0(ChannelHandlerContext ctx, I msg)」,每當Netty調用這個方法,對象「I」是消息,這裏使用了Java的泛型設計,程序就能處理I。如何處理消息徹底取決於程序的須要。在處理消息時有一點須要注意的,在Netty中事件處理IO通常有不少線程,程序中儘可能不要阻塞IO線程,由於阻塞會下降程序的性能。

必須不阻塞IO線程意味着在ChannelHandler中使用阻塞操做會有問題。幸運的是Netty提供瞭解決方案,咱們能夠在添加ChannelHandler到ChannelPipeline中時指定一個EventExecutorGroup,EventExecutorGroup會得到一個EventExecutor,EventExecutor將執行ChannelHandler的全部方法。EventExecutor將使用不一樣的線程來執行和釋放EventLoop。

相關文章
相關標籤/搜索