請先參考我以前的博文JAVA學習筆記--3.Network IO的 NIO(NonBlocking IO) SOCKET 章節。這裏主要講下JAVA NIO其中幾個比較被忽略的細節,不求全,歡迎補充。html
Selectjava
當調用ServerSocketChannel.accept();
時,若是該channel處於非阻塞狀態並且沒有等待(pending)的鏈接,那麼該方法會返回null;不然該方法會阻塞直到鏈接可用或者發生I/O錯誤。此時實際上Client發送了connect請求而且服務端是處於non-blocking模式下,那麼這個accept()會返回一個不爲null的channel。算法
當調用SocketChannel.connect()
時,若是該channel出於non-blocking模式,只有在本地鏈接下才可能當即完成鏈接,並返回true;在其餘狀況下,該方法返回false,而且必須在後面調用 SocketChannel.finishConnect
方法。若是SocketChannel.finishConnect
的執行結果爲true,才意味着鏈接真正創建。編程
SocketChannel.write()
方法內部不涉及緩衝,它直接把數據寫到socket的send buffer中。設計模式
每次迭代selector.keys()完時,記得remove該SelectionKey,防止發生CPU100%的問題。api
一般不應register OP_WRITE,通常來講socket 緩衝區老是可寫的,僅在write()方法返回0時或者未徹底寫完數據才須要register OP_WRITE操做。當數據寫完的時候,須要deregister OP_WRITE。 socket空閒時,即爲可寫.有數據來時,可讀。對於nio的寫事件,只在發送數據時,若是由於通道的阻塞,暫時不能所有發送,才註冊寫事件key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
。等通道可寫時,再寫入。同時判斷是否寫完,若是寫完,就取消寫事件便可key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
。 空閒狀態下,全部的通道都是可寫的,若是你給每一個通道註冊了寫事件,那麼確定是死循環了,致使發生CPU100%的問題。詳見Netty的NioSocketChannel的父類方法setOpWrite()和clearOpWrite()方法。這個在iteye裏面的帖子Java nio網絡編程的一點心得也有說明。網絡
在register OP_CONNECT後,而且觸發OP_CONNECT後,須要再deregister OP_CONNECT。詳見Netty的NioEventLoop.processSelectedKey()方法後半部分。多線程
另外,須要注意到Selector.wakeup()是一個昂貴的操做,通常須要減小是用。詳見NioEventLoop.run()方法內的處理方式。架構
還有各類各樣的坑,實在太多了。詳見Java NIO框架的實現技巧和陷阱框架
雖然筆者閱讀完幾個重要的交互過程,可是代碼細節較多,沒法體現NETTY對NIO的使用。因而畫了兩幅圖,與讀者共享。
事先說明下,下圖圖片上的標記2表示是一個新的EventLoop線程,非main線程。
另外,雖然Netty中大量使用了ChannelFuture異步,可是換種方式理解的話,能夠理解爲同步執行。
這裏有個細節,就是boss線程註冊了OP_ACCEPT線程,而後當接收到客戶端的請求時,會使用work線程池裏面的線程來處理客戶端請求。代碼細節在NioServerSocketChannel.doReadMessages
的tag1.1.2處以及child.unsafe().register(child.newPromise());
處。
從Netty的Maven倉庫能夠看 到,大體分爲以下模塊:
綜上,能夠看出,首先對框架機制和傳輸的數據進行了抽象,而後又對數據如何在框架中傳輸進行了抽象。
下面的介紹的思想比較零散,不成系統,主要是閱讀代碼過程當中產生的碰撞,供讀者參考。
Netty 一樣也採用了多個Selector並存的結構,主要緣由是單個Selector的結構在支撐大量鏈接事件的管理和觸發已經遇到瓶頸。
Bootstrap.channel()方法經過傳遞class對象,而後經過反射來實例化具體的Channel實例,必定程度上避免了寫死類名字符串致使將來版本變更時發生錯誤的可能性。
框架必備的類,你們還能夠看下common工程,裏面真是一個寶藏。
InternalLogger
用來避免對第三方日誌框架的依賴,如slf,log4j等等。ChannelOption
靈活地使用泛型機制,避免用戶設置參數發生低級錯誤。SystemPropertyUtil
提供對xt屬性的訪問方法DefaultThreadFactory
提供自定義線程工程類,方便定位問題。PlatformDependent
若是須要支持不一樣平臺的話,能夠把平臺相關的操做都放在一塊兒進行管理。打印日誌時,能夠參考這樣來拼接參數,String.format("nThreads: %d (expected: > 0)", nThreads)
@Sharable表示該類是無狀態的,僅僅起「文檔」標記做用。
在子接口裏僅僅把父接口方法返回值覆寫了,而後什麼都不作。這樣必定程度上避免了強制轉型的尷尬。
把前一個future做爲下一個調用方法的參數,這樣能夠異步執行。而後後面的邏輯能夠先判斷future結果後再進行處理,從而提高性能。
addLast0 等以0爲結尾的方法表示私有的含義。
有些方法的getter、setter前綴省略,有點相似jQuery裏面命名風格。不過不太建議在本身的產品中使用這種風格,儘可能使用和和產品內的同樣的命名風格。
心中存疑,請你們不吝賜教。
在NioEventLoop.run()方法中,好像每次用完SelectionKey沒有remove 掉它,可能和SelectedSelectionKeySet實現機制有關。可是沒直接看出來具體之間的聯繫,
爲何boss也要是個線程池?目前來看,服務端2個線程保持不變,main線程出於wait狀態,boss線程池其中的一個線程進行接收客戶端鏈接請求,而後把請求轉發給worker線程池。
javaChannel().register(eventLoop().selector, 0, this);
,爲何ops參數默認是0?
socketChannel.register() and key.interestOps()
仍是有點不太明白,估計個人思路鑽進死衚衕了。
b.bind(port)這個裏面的內容很是複雜,不只僅是bind一個port那麼簡單。因此該方法命名不是很好。
父接口依賴子接口,也不是很好。
DefaultChannelPipeline.addLast 這個方法太坑了,並非把handler加到最後一個上面,而是加到tail前一個。
無一類外的是,繼承體系相對複雜。父類,子類的命名一般不能體現出誰是父類,誰是子類,除了一個Abstract可以直接看出來。
客戶端單實例,防止消耗過多線程。這個在http://hellojava.info/中屢次提到。
目前網上的NIO例子都基本都不太靠譜,BUG多多。建議能夠參考下《JAVA 分佈式JAVA應用 基礎與實踐》。
JAVA NIO不必定就比OIO快,重點是更加Scalable。
框架幫趟坑,不要輕易製造輪子,除非現有輪子不好勁。JAVA NIO裏面的坑太多了,Netty裏面的大量的issue充分說明了問題。技術某種程度上不是最重要的,若是你們努力程度差很少的話,技術上不會差到哪裏去。開源產品主要是生態圈的建設。
接收對端數據時,數據經過netty從網絡中讀取,進行其餘各類處理,而後供應用程序使用。發送本地數據時,應用程序首先完成數據處理, 而後經過netty進行各類處理,最終把數據發出。可是爲何要區分inbound,outbound,或者說提供了head->tail以及tail->head的遍歷?
當咱們跳出裏面的細節時,考慮一下,若是你是做者的話,會如何考慮。總體的一個算法 。不一樣的通信模型,nio,sun jdk bug, option(默認和用戶設置),異步future、executor,pipeline、context、handler, 設計模式 。
我想象中操做應該是這樣的,handler鏈管理不須要走pipeline,event鏈也不須要走pipiline,僅僅對數據的發送,接收和操做才走pipeline。事件機制應該起一些加強型、輔助型做用,不該該影響到核心流程的執行或者起到什麼關鍵做用,主要是應該給第三方擴展用。而netty中,全部的一切都要走pipelne。
太多異步,怎麼測試?
招式vs心法。招式,至關於api;心法,至關於api工做原理,利與弊。仍是要理解底層,不然仍是可能理解不清楚。cpu,內存,io,網絡,而最終浮於招式。
看完了麼?知道了Netty是什麼,內部大概是怎麼運做的,可是有些細節還不知道爲何。真的就理解精髓了麼?NIO的坑.. 底層OS的坑.. Netty的精髓是在於對各類細節的處理,坑的處理,對性能的處理,而不是僅僅一個XX模式運用。JAVA NIO細節和坑實在太多,估計再給我一週的時間,也研究不完。有點小沮喪。
Stackoverflow:Java NIO - using Selectors
Stackoverflow:Difference between socketChannel.register() and key.interestOps()