如何封裝一個非阻塞式 Java 網(wǎng)絡(luò)庫
1. 前言
到目前為止,我們已經(jīng)學(xué)完了 Java 網(wǎng)絡(luò)編程的所有關(guān)鍵知識點(diǎn),如果能將這些知識點(diǎn)靈活應(yīng)用到項(xiàng)目中,那再好不過了。本小節(jié)將從實(shí)戰(zhàn)的角度出發(fā),展示如何基于 Java NIO 封裝一個非阻塞式網(wǎng)絡(luò)庫。核心是采用事件反應(yīng)器模型,將復(fù)雜的 SocketChannel、ServerSocketChannel、ByteBuffer、Selector 進(jìn)行抽象,將繁瑣的數(shù)據(jù)讀寫操作加以封裝,以便應(yīng)用程序調(diào)用。
本項(xiàng)目提供的完整代碼路徑:
2. 系統(tǒng)類圖
在 OOD 的思想體系下,現(xiàn)實(shí)世界中無論是具體的、還是抽象的事物,都是對象,現(xiàn)實(shí)世界就是由對象組成的。對象有自己的屬性和行為,對象之間可以產(chǎn)生聯(lián)系。把具有相同屬性和行為的對象叫做同一類對象,抽象出類。OOD 的思想非常符合人的思維方式,更容易建模和抽象。如果你對 OOD 不是很熟悉的話,可以查閱相關(guān)資料學(xué)習(xí)。
我們現(xiàn)在的工作是要把 Java NIO 中的各個組件加以抽象,抽象出一個或若干個類,通過 UML 展現(xiàn)出來,類圖如下:
下來我們就解釋一下以上幾個接口和實(shí)現(xiàn)類的功能:
類名 | 功能 |
---|---|
Poller | Java NIO Selector 事件多路復(fù)用機(jī)制的封裝,實(shí)現(xiàn)事件循環(huán)機(jī)制,是一個事件反應(yīng)器,是一個功能實(shí)現(xiàn)類 |
IOHandler | 是 Poller 的配套類,響應(yīng)accept、connect、read、write事件,是一個 Java 接口 |
SocketHandler | 是一個功能類,實(shí)現(xiàn)了 IOHandler 接口,將一些通用的邏輯加以封裝 |
Acceptor | 實(shí)現(xiàn) TCP 服務(wù)器監(jiān)聽功能,繼承自 SocketHandler |
TcpHandler | TCP 客戶端、服務(wù)器邏輯的封裝,繼承自 SocketHandler。主要完成客戶端連接,服務(wù)器接收新連接,數(shù)據(jù)收發(fā),關(guān)閉連接的功能 |
IOAdapter | 事件循環(huán)機(jī)制向應(yīng)用層提供的一個回調(diào)接口,一般由 IOHandler 調(diào)用 |
AbstractAdapter | 是一個 Java 接口,主要完成通用邏輯,實(shí)現(xiàn)了 IOAdapter 接口 |
Listener | 事件監(jiān)聽接口,主要實(shí)現(xiàn)線程切換的功能 |
CustomEventObject | 代表一個具體事件,是 Listener 的配套類 |
IOThread | 對 Java 線程的封裝,聚合了 Poller 功能。我們說過 Java 的 Selector 其實(shí)是同步的,需要一個線程調(diào)用 select 監(jiān)聽事件 |
ThreadPool | 對 IOThread 的封裝,提供線程池功能 |
3. 接口設(shè)計(jì)
軟件的接口是指軟件模塊對外提供的一組函數(shù)或者方法,目的是讓別的模塊訪問本模塊的功能,以達(dá)到組件復(fù)用的目的。根據(jù)模塊邏輯復(fù)雜度的不同,接口由分為:系統(tǒng)接口、子系統(tǒng)接口、模塊接口、子模塊接口、類接口。關(guān)于模塊、子模塊、類的應(yīng)用都非常靈活,我們這里認(rèn)為類就是最小的模塊。
本小節(jié)所說的接口是指 Java 接口。我們抽象了三個 Java 接口:Listener、IOHandler、IOAdapter,還有一個功能類 Poller?,F(xiàn)在對每個接口中的方法加以說明:
- Poller
類名 | 接口名 | 描述 |
---|---|---|
Poller | register | 將 IOHandler 實(shí)例添加到 Poller |
start | 啟動一個 Poller 實(shí)例 | |
close | 停止一個 Poller 實(shí)例 | |
poll | Poller 進(jìn)入事件循環(huán) |
- IOHandler
類名 | 接口名 | 描述 |
---|---|---|
IOHandler | handle_read | 是一個回調(diào)方法,當(dāng) Poller 監(jiān)聽到某個 IOHandler 注冊的讀事件觸發(fā)時(shí),調(diào)用 handle_read |
handle_write | 是一個回調(diào)方法,當(dāng) Poller 監(jiān)聽到某個 IOHandler 注冊的寫事件觸發(fā)時(shí),調(diào)用 handle_write | |
handle_accept | 是一個回調(diào)方法,當(dāng) Poller 監(jiān)聽到某個 IOHandler 注冊的accept事件觸發(fā)時(shí),調(diào)用 handle_accept | |
handle_connected | 是一個回調(diào)方法,當(dāng) Poller 監(jiān)聽到某個 IOHandler 注冊的connected事件觸發(fā)時(shí),調(diào)用 handle_connected | |
getSocketChannel | 用于獲取 IOHandler 對應(yīng)的 SocketChannel 對象 |
IOHandler 是一個抽象接口,TcpHandler 需要實(shí)現(xiàn)此接口,當(dāng)然你也可以實(shí)現(xiàn)其他協(xié)議,只需要擴(kuò)展 IOHandler 的接口即可。
- IOAdapter
類名 | 接口名 | 描述 |
---|---|---|
IOAdapter | onAccept | 當(dāng) IOHandler 收到一個新的 TCP 連接時(shí),在 handle_read 中回調(diào)此方法 |
onConnected | 當(dāng) io_hanIOHandlerdler 完成異步連接時(shí),在 handle_connected 中回調(diào)此方法 | |
onRead | IOHandler 會在 handle_read 中回調(diào)此方法 | |
onWrite | IOHandler 會在 handle_write 中回調(diào)此方法 | |
onClose | IOHandler 收到連接被關(guān)閉時(shí),回調(diào)此方法 | |
setSocketHandler | 向 IOAdapter 設(shè)置一個 IOHandler 對象 |
應(yīng)用層需要實(shí)現(xiàn) IOAdapter 的接口,并且要覆蓋接口中的方法,完成數(shù)據(jù)收發(fā)。
- Listener
類名 | 接口名 | 描述 |
---|---|---|
Listener | process | 處理異步事件 |
4. 總結(jié)
本小節(jié)主要是將前面小節(jié)介紹的 Java NIO 相關(guān)模塊進(jìn)行一個抽象,形成一個非阻塞的 Java 網(wǎng)絡(luò)庫。采用的設(shè)計(jì)思想就是依賴倒轉(zhuǎn),將復(fù)雜的網(wǎng)絡(luò)編程細(xì)節(jié)進(jìn)行封裝,讓應(yīng)用程序員不需要關(guān)注這些復(fù)雜的機(jī)制。
我們抽象出了一組接口和一組功能類,并對類的功能和接口中的方法進(jìn)行了一一說明。本小節(jié)可以說是對整個系列內(nèi)容的總結(jié)和實(shí)踐。