ServerBootstrap 負責初始話netty服務器,而且開始監聽端口的socket請求。 java
Java代碼 bootstrap
bootstrap bootstrap = new ServerBootstrap( new NioServerSocketChannelFactory( Executors.newCachedThreadPool(),//boss線程池 Executors.newCachedThreadPool()//worker線程池 ) ); bootstrap.setPipelineFactory(new HttpChannelPipelineFactory()); bootstrap.setOption("child.tcpNoDelay", true); bootstrap.setOption("child.keepAlive", true); bootstrap.bind(new InetSocketAddress(httpPort));//端口開始監聽
ServerBootstrap 用一個ServerSocketChannelFactory 來實例化。ServerSocketChannelFactory 有兩種選擇,一種是NioServerSocketChannelFactory,一種是OioServerSocketChannelFactory。 前者使用NIO,後則使用普通的阻塞式IO。它們都須要兩個線程池實例做爲參數來初始化,一個是boss線程池,一個是worker線程池。 瀏覽器
ServerBootstrap.bind(int)負責綁定端口,當這個方法執行後,ServerBootstrap就能夠接受指定端口上的socket鏈接了。一個ServerBootstrap能夠綁定多個端口。 服務器
可 以這麼說,ServerBootstrap監聽的一個端口對應一個boss線程,它們一一對應。好比你須要netty監聽80和443端口,那麼就會有兩 個boss線程分別負責處理來自兩個端口的socket請求。在boss線程接受了socket鏈接求後,會產生一個channel(一個打開的 socket對應一個打開的channel),並把這個channel交給ServerBootstrap初始化時指定的 ServerSocketChannelFactory來處理,boss線程則繼續處理socket的請求。 網絡
ServerSocketChannelFactory則會從worker線程池中找出一個worker線程來繼續處理這個請求。
如 果是OioServerSocketChannelFactory的話,那個這個channel上全部的socket消息消息,從開始到 channel(socket)關閉,都只由這個特定的worker來處理,也就是說一個打開的socket對應一個指定的worker線程,這個 worker線程在socket沒有關閉的狀況下,也只能爲這個socket處理消息,沒法服務器他socket。 架構
若是是NioServerSocketChannelFactory的話則否則,每一個worker能夠服務不一樣的socket或者說channel,worker線程和channel再也不有一一對應的關係。
顯然,NioServerSocketChannelFactory只須要少許活動的worker線程及能很好的處理衆多的channel,而OioServerSocketChannelFactory則須要與打開channel等量的worker線程來服務。 併發
線 程是一種資源,因此當netty服務器須要處理長鏈接的時候,最好選擇NioServerSocketChannelFactory,這樣能夠避免建立大 量的worker線程。在用做http服務器的時候,也最好選擇NioServerSocketChannelFactory,由於現代瀏覽器都會使用 http keepalive功能(可讓瀏覽器的不一樣http請求共享一個信道),這也是一種長鏈接。 框架
當 某個channel有消息到達或者有消息須要寫入socket的時候,worker線程就會從線程池中取出一個。在worker線程中,消息會通過設定好 的ChannelPipeline處理。ChannelPipeline就是一堆有順序的filter,它分爲兩部分:UpstreamHandler和 DownStreamHandler。本文着重介紹netty的線程模型,因此關於pipeline的內容從簡說明。 異步
客戶端送入的消息會首先由許多UpstreamHandler依次處理,處理獲得的數據送入應用的業務邏輯handler,一般用SimpleChannelUpstreamHandler來實現這一部分。 jvm
對 於Nio當messageReceived()方法執行後,若是沒有產生異常,worker線程就執行完畢了,它會被線程池回收。業務邏輯hanlder 會經過一些方法,把返回的數據交給指定好順序的DownStreamHandler處理,處理後的數據若是須要,會被寫入channel,進而經過綁定的 socket發送給客戶端。這個過程是由另一個線程池中的worker線程來完成的。
對於Oio來講,從始到終,都是由一個指定的worker來處理。
worker 線程是由netty內部管理,統一調配的一種資源,因此最好應該儘快的把讓worker線程執行完畢,返回給線程池回收利用。worker線程的大部分時 間消耗在在ChannelPipeline的各類handler中,而在這些handler中,通常是負責應用程序業務邏輯摻入的那個handler最佔 時間,它一般是排在最後的UpstreamHandler。因此經過把這部分處理內容交給另一個線程來處理,能夠有效的減小worker線程的週期循環 時間。通常有兩種方法:
messageReceived()方法中開啓一個新的線程來處理業務邏輯
在messageReceived()中開啓一個新線程來繼續處理業務邏輯,而worker線程在執行完messageReceived()就會結束了。更加優雅的方法是另外構造一個線程池來提交業務邏輯處理任務。
基本使用方法:
把 共享的ExecutionHandler實例放在業務邏輯handler以前便可,注意ExecutionHandler必定要在不一樣的pipeline 之間共享。它的做用是自動從ExecutionHandler本身管理的一個線程池中拿出一個線程來處理排在它後面的業務邏輯handler。而 worker線程在通過ExecutionHandler後就結束了,它會被ChannelFactory的worker線程池所回收。
它的構造方法是ExecutionHandler(Executor executor) ,很顯然executor就是ExecutionHandler內部管理的線程池了。netty額外給咱們提供了兩種線程池:
MemoryAwareThreadPoolExecutor和OrderedMemoryAwareThreadPoolExecutor,它們都在org.jboss.netty.handler.execution 包下。
MemoryAwareThreadPoolExecutor 確保jvm不會由於過多的線程而致使內存溢出錯誤,OrderedMemoryAwareThreadPoolExecutor是前一個線程池的子類,除 了保證沒有內存溢出以外,還能夠保證channel event的處理次序。具體能夠查看API文檔,上面有詳細說明。
Netty服務端啓動步驟:
代碼:
Netty提供NIO與BIO兩種模式,咱們主要關心NIO的模式:
NIO處理方式:
1.Netty用一個BOSS線程去處理客戶端的接入,建立Channel
2.從WORK線程池(WORK線程數量默認爲cpu cores的2倍)拿出一個WORK線程交給BOSS建立好的Channel實例(Channel實例持有java網絡對象)
3.WORK線程進行數據讀入(讀到ChannelBuffer)
4.接着觸發相應的事件傳遞給ChannelPipeline進行業務處理(ChannelPipeline中包含一系列用戶自定義的ChannelHandler組成的鏈)
有 一點要注意的是,執行整個ChannelHandler鏈這個過程是串行的,若是業務邏輯(好比DB操做)比較耗時,會致使WORK線程長時間被佔用得不 到釋放,最終影響整個服務端的併發處理能力,因此通常咱們經過ExecutionHandler線程池來異步處理ChannelHandler調用鏈,使 得WORK線程通過ExecutionHandler時獲得釋放。
要解決這個問題增長下面代碼便可:
Netty爲ExecutionHandler提供了兩種可選的線程池模型:
1) MemoryAwareThreadPoolExecutor
經過對線程池內存的使用控制,可控制Executor中待處理任務的上限(超過上限時,後續進來的任務將被阻塞),並可控制單個Channel待處理任務的上限,防止內存溢出錯誤;
2) OrderedMemoryAwareThreadPoolExecutor
是 1)的子類。除了MemoryAwareThreadPoolExecutor 的功能以外,它還能夠保證同一Channel中處理的事件流的順序性,這主要是控制事件在異步處理模式下可能出現的錯誤的事件順序,但它並不保證同一 Channel中的事件都在一個線程中執行,也不必保證這個。
咱們看下OrderedMemoryAwareThreadPoolExecutor中的註釋:
Thread X: --- Channel A (Event A1) --. .-- Channel B (Event B2) --- Channel B (Event B3) --->
\ /
X
/ \
Thread Y: --- Channel B (Event B1) --' '-- Channel A (Event A2) --- Channel A (Event A3) --->
處理同一個Channel的事件,是串行的方式執行的,可是同一個Channel的多個事件,可能會分佈到線程中池中的多個線程去處理,不一樣的Channel事件能夠併發處理,互相併不影響
再來看看MemoryAwareThreadPoolExecutor中的註釋
Thread X: --- Channel A (Event 2) --- Channel A (Event 1) --------------------------->
Thread Y: --- Channel A (Event 3) --- Channel B (Event 2) --- Channel B (Event 3) --->
Thread Z: --- Channel B (Event 1) --- Channel B (Event 4) --- Channel A (Event 4) --->
同 一個Channel的事件,並不保證處理順序,可能一個線程先處理了Channel A (Event 3),而後另外一個線程才處理Channel A (Event 2),若是業務不要求保證事件的處理順序,我認爲仍是儘可能使用MemoryAwareThreadPoolExecutor比較好
Netty採用標準的SEDA(Staged Event-Driven Architecture) 架構
SEDA的核心思想是把一個請求處理過程分紅幾個Stage,不一樣資源消耗的Stag使用不一樣數量的線程來處理,Stag間使用事件驅動的異步通訊模式。更進一步,在每一個Stage中能夠動態配置本身的線程數,在超載時降級運行或拒絕服務。
Netty所設計的事件類型,表明了網絡交互的各個階段,每一個階段發生時,會觸發相應的事件並交給ChannelPipeline進行處理。事件處理都是經過Channels類中的靜態方法調用開始的。
Channels中事件流轉靜態方法:
1. fireChannelOpen
2. fireChannelBound
3. fireChannelConnected
4. fireMessageReceived
5. fireWriteCompleteLater
6. fireWriteComplete
7. fireChannelInterestChangedLater
8. fireChannelDisconnectedLater
9. fireChannelDisconnected
10. fireChannelUnboundLater
11. fireChannelUnbound
12. fireChannelClosedLater
13. fireChannelClosed
14. fireExceptionCaughtLater
15. fireExceptionCaught
16. fireChildChannelStateChanged
Netty將網絡事件分爲兩種類型:
1.Upstresam:上行,主要是由網絡底層反饋給Netty的,好比messageReceived、channelConnected
2.Downstream:下行,框架本身發起的,好比bind、write、connect等
Netty的ChannelHandler 分爲3種類型:
1.只處理Upstream事件:實現ChannelUpstreamHandler接口
2.只處理Downstream事件:實現ChannelDownstreamHandler接口
3.同時處理Upstream和Downstream事件:同時實現ChannelUpstreamHandler和ChannelDownstreamHandler接口
ChannelPipeline 維持全部ChannelHandler的有序鏈表,當有Upstresam或Downstream網絡事件發生時,調用匹配事件類型的 ChannelHandler來處理。ChannelHandler自身能夠控制是否要流轉到調用鏈中的下一個 ChannelHandler(ctx.sendUpstream(e)或者ctx.sendDownstream(e)),這同樣有一個好處,好比業務 數據Decoder出現非法數據時沒必要繼續流轉到下一個ChannelHandler
下面是我胡亂的畫的一個圖: