Getty – Java NIO 框架設計與實現
前言
Getty是我為了學習 Java NIO 所寫的一個 NIO 框架,實現過程中參考了 Netty 的設計,同時使用 Groovy 來實現。雖然只是玩具,但是麻雀雖小,五臟俱全,在實現過程中,不僅熟悉了 NIO 的使用,還借鑑了很多 Netty 的設計思想,提升了自己的編碼和設計能力。
至於為什麼用 Groovy 來寫,因為我剛學了 Groovy,正好拿來練手,加上 Groovy 是相容 Java 的,所以只是語法上的差別,底層實現還是基於 Java API的。
Getty 的核心程式碼行數不超過 500 行,一方面得益於 Groovy 簡潔的語法,另一方面是因為我只實現了核心的邏輯,最複雜的其實是解碼器實現。腳手架容易搭,摩天大樓哪有那麼容易蓋,但用來學習 NIO 足以。
執行緒模型
Getty 使用的是 Reactor 多執行緒模型
- 有專門一個 NIO 執行緒- Acceptor 執行緒用於監聽服務端,接收客戶端的 TCP 連線請求,然後將連線分配給工作執行緒,由工作執行緒來監聽讀寫事件。
- 網路 IO 操作-讀/寫等由多個工作執行緒負責,由這些工作執行緒負責訊息的讀取、解碼、編碼和傳送。
- 1 個工作執行緒可以同時處理N條鏈路,但是 1 個鏈路只對應 1 個工作執行緒,防止發生併發操作問題。
事件驅動模型
整個服務端的流程處理,建立於事件機制上。在 [接受連線->讀->業務處理->寫 ->關閉連線 ]這個過程中,觸發器將觸發相應事件,由事件處理器對相應事件分別響應,完成伺服器端的業務處理。
事件定義
onRead
:當客戶端發來資料,並已被工作執行緒正確讀取時,觸發該事件 。該事件通知各事件處理器可以對客戶端發來的資料進行實際處理了。onWrite
:當客戶端可以開始接受服務端傳送資料時觸發該事件,通過該事件,我們可以向客戶端傳送響應資料。(當前的實現中並未使用寫事件)onClosed
:當客戶端與伺服器斷開連線時觸發該事件。
事件回撥機制的實現
在這個模型中,事件採用廣播方式,也就是所有註冊的事件處理器都能獲得事件通知。這樣可以將不同性質的業務處理,分別用不同的處理器實現,使每個處理器的功能儘可能單一。
如下圖:整個事件模型由監聽器、事件介面卡、事件觸發器(HandlerChain,PipeLine)、事件處理器組成。
ServerListener
:這是一個事件介面,定義需監聽的伺服器事件interface ServerListener extends Serializable{ /** * 可讀事件回撥 * @param request */ void onRead(ctx) /** * 可寫事件回撥 * @param request * @param response */ void onWrite(ctx) /** * 連線關閉回撥 * @param request */ void onClosed(ctx) }
EventAdapter
:對 Serverlistener 介面實現一個介面卡 (EventAdapter),這樣的好處是最終的事件處理器可以只處理所關心的事件。class EventAdapter implements ServerListener { //下個處理器的引用 protected next void onRead(Object ctx) { } void onWrite(Object ctx) { } void onClosed(Object ctx) { } }
Notifier
:用於在適當的時候通過觸發伺服器事件,通知在冊的事件處理器對事件做出響應。interface Notifier extends Serializable{ /** * 觸發所有可讀事件回撥 */ void fireOnRead(ctx) /** * 觸發所有可寫事件回撥 */ void fireOnWrite(ctx) /** * 觸發所有連線關閉事件回撥 */ void fireOnClosed(ctx) }
HandlerChain
:實現了Notifier
介面,維持有序的事件處理器鏈條,每次從第一個處理器開始觸發。class HandlerChain implements Notifier{ EventAdapter head EventAdapter tail /** * 新增處理器到執行鏈的最後 * @param handler */ void addLast(handler) { if (tail != null) { tail.next = handler tail = tail.next } else { head = handler tail = head } } void fireOnRead(ctx) { head.onRead(ctx) } void fireOnWrite(ctx) { head.onWrite(ctx) } void fireOnClosed(ctx) { head.onClosed(ctx) } }
PipeLine
:實現了Notifier
介面,作為事件匯流排,維持一個事件鏈的列表。class PipeLine implements Notifier{ static logger = LoggerFactory.getLogger(PipeLine.name) //監聽器佇列 def listOfChain = [] PipeLine(){} /** * 新增監聽器到監聽佇列中 * @param chain */ void addChain(chain) { synchronized (listOfChain) { if (!listOfChain.contains(chain)) { listOfChain.add(chain) } } } /** * 觸發所有可讀事件回撥 */ void fireOnRead(ctx) { logger.debug("fireOnRead") listOfChain.each { chain -> chain.fireOnRead(ctx) } } /** * 觸發所有可寫事件回撥 */ void fireOnWrite(ctx) { listOfChain.each { chain -> chain.fireOnWrite(ctx) } } /** * 觸發所有連線關閉事件回撥 */ void fireOnClosed(ctx) { listOfChain.each { chain -> chain.fireOnClosed(ctx) } } }
事件處理流程
程式設計模型
事件處理採用職責鏈模式,每個處理器處理完資料之後會決定是否繼續執行下一個處理器。如果處理器不將任務交給執行緒池處理,那麼整個處理流程都在同一個執行緒中處理。而且每個連線都有單獨的PipeLine
,工作執行緒可以在多個連線上下文切換,但是一個連線上下文只會被一個執行緒處理。
核心類
ConnectionCtx
連線上下文ConnectionCtx
class ConnectionCtx { /**socket連線*/ SocketChannel channel /**用於攜帶額外引數*/ Object attachment /**處理當前連線的工作執行緒*/ Worker worker /**連線超時時間*/ Long timeout /**每個連線擁有自己的pipeline*/ PipeLine pipeLine }
NioServer
主執行緒負責監聽埠,持有工作執行緒的引用(使用輪轉法分配連線),每次有連線到來時,將連線放入工作執行緒的連線佇列,並喚醒執行緒selector.wakeup()
(執行緒可能阻塞在selector
上)。
class NioServer extends Thread { /**服務端的套接字通道*/ ServerSocketChannel ssc /**選擇器*/ Selector selector /**事件匯流排*/ PipeLine pipeLine /**工作執行緒列表*/ def workers = [] /**當前工作執行緒索引*/ int index }
Worker
工作執行緒,負責註冊server傳遞過來的socket連線。主要監聽讀事件,管理socket,處理寫操作。
class Worker extends Thread { /**選擇器*/ Selector selector /**讀緩衝區*/ ByteBuffer buffer /**主執行緒分配的連線佇列*/ def queue = [] /**儲存按超時時間從小到大的連線*/ TreeMap<Long, ConnectionCtx> ctxTreeMap void run() { while (true) { selector.select() //註冊主執行緒傳送過來的連線 registerCtx() //關閉超時的連線 closeTimeoutCtx() //處理事件 dispatchEvent() } } }
執行一個簡單的 Web 伺服器
我實現了一系列處理HTTP
請求的處理器,具體實現看程式碼。
LineBasedDecoder
:行解碼器,按行解析資料HttpRequestDecoder
:HTTP請求解析,目前只支援GET請求HttpRequestHandler
:Http 請求處理器,目前只支援GET方法HttpResponseHandler
:Http響應處理器
下面是寫在test
中的例子
class WebServerTest { static void main(args) { def pipeLine = new PipeLine() def readChain = new HandlerChain() readChain.addLast(new LineBasedDecoder()) readChain.addLast(new HttpRequestDecoder()) readChain.addLast(new HttpRequestHandler()) readChain.addLast(new HttpResponseHandler()) def closeChain = new HandlerChain() closeChain.addLast(new ClosedHandler()) pipeLine.addChain(readChain) pipeLine.addChain(closeChain) NioServer nioServer = new NioServer(pipeLine) nioServer.start() } }
另外,還可以使用配置檔案getty.properties
設定程式的執行引數。
#用於拼接訊息時使用的二進位制陣列的快取區 common_buffer_size=1024 #工作執行緒讀取tcp資料的快取大小 worker_rcv_buffer_size=1024 #監聽的埠 port=4399 #工作執行緒的數量 worker_num=1 #連線超時自動斷開時間 timeout=900 #根目錄 root=.
總結
Getty是我造的第二個小輪子,第一個是RedisHttpSession。都說不要重複造輪子。這話我是認同的,但是掌握一門技術最好的方法就是實踐,在沒有合適專案可以使用新技術的時候,造一個簡單的輪子是不錯的實踐手段。
Getty 的缺點或者說還可以優化的點:
- 執行緒的使用直接用了
Thread
類,看起來有點low。等以後水平提升了再來抽象一下。 - 目前只有讀事件是非同步的,寫事件是同步的。未來將寫事件也改為非同步的。
相關文章
- Java NIO程式設計示例Java程式設計
- Java網路程式設計和NIO詳解9:基於NIO的網路程式設計框架NettyJava程式設計框架Netty
- 基於Java、Kafka、ElasticSearch的搜尋框架的設計與實現JavaKafkaElasticsearch框架
- 機器學習可視分析框架設計與實現機器學習框架
- Java通過SSLEngine與NIO實現HTTPS訪問JavaHTTP
- NIO、BIO、AIO 與 PHP 實現AIPHP
- Java網路程式設計和NIO詳解7:淺談 Linux 中NIO Selector 的實現原理Java程式設計Linux
- JAVA NIO程式設計入門(二)Java程式設計
- JAVA NIO程式設計入門(一)Java程式設計
- JAVA NIO 程式設計入門(三)Java程式設計
- Java網路程式設計和NIO詳解3:IO模型與Java網路程式設計模型Java程式設計模型
- Java網路程式設計與NIO詳解10:深度解讀Tomcat中的NIO模型Java程式設計Tomcat模型
- Java網路程式設計和NIO詳解6:Linux epoll實現原理詳解Java程式設計Linux
- Java網路程式設計與NIO詳解11:Tomcat中的Connector原始碼分析(NIO)Java程式設計Tomcat原始碼
- Java NIO框架Mina、Netty、Grizzly介紹與對比Java框架Netty
- 使用Java NIO 和 NIO2實現檔案輸入/輸出Java
- Redis設計與實現Redis
- 《redis設計與實現》Redis
- Java中的多級快取設計與實現Java快取
- Java引用計數與實現Java
- Java網路程式設計與NIO詳解4:淺析NIO包中的Buffer、Channel 和 SelectorJava程式設計
- Java NIO通訊框架在電信領域的實踐Java框架
- Java NIO - Channel 與 SelectorJava
- 網路程式設計NIO:BIO和NIO程式設計
- 關於介面測試——自動化框架的設計與實現框架
- 打破虛擬與現實邊界的遊戲設計理論框架遊戲設計框架
- iOS開源照片瀏覽器框架SGPhotoBrowser的設計與實現iOS瀏覽器框架
- Titan 的設計與實現
- LFU 的設計與實現
- Cuckoo Filter:設計與實現Filter
- API的設計與實現API
- nio的實現原理
- Java網路程式設計與NIO詳解8:淺析mmap和Direct BufferJava程式設計
- Android Binder設計與實現 - 設計篇Android
- 高德智慧景區隨身聽播放器框架設計與實現播放器框架
- Java單例設計模式的理解與常規實現方式Java單例設計模式
- Triple 協議支援 Java 異常回傳的設計與實現協議Java
- Java Web 模板程式碼生成器的設計與實現JavaWeb