非阻塞 Java NIO Channel 體系介紹
1. 前言
java.nio.channels.SocketChannel 和 java.nio.channels.ServerSocketChannel 是編寫(xiě)非阻塞 Java TCP Soccket 程序的重要模塊。然而,Channel 是 Java NIO 非常重要的概念,在 java.nio.channels 抽象了完整的、關(guān)于 Channel 的接口(Interface) 層次結(jié)構(gòu)。
Channel 表示一個(gè)和硬件設(shè)備、磁盤(pán)文件、網(wǎng)絡(luò) Socket 等 I/O 設(shè)備、或者組件之間連接。Channel 的狀態(tài)要么是打開(kāi)的,要么是關(guān)閉的。當(dāng) Channel 處于打開(kāi)狀態(tài),可以從 Channel 中讀取數(shù)據(jù)并且保存到 ByteBuffer 中,也可以將 ByteBuffer 中的數(shù)據(jù)寫(xiě)到 Channel 中。當(dāng) Channel 處于關(guān)閉狀態(tài),對(duì) Channel 執(zhí)行讀、寫(xiě)操作,會(huì)產(chǎn)生 ClosedChannelException 異常。
2. Channel 類(lèi)層次結(jié)構(gòu)
java.nio.channels 包抽象了完整的 Channel 層次結(jié)構(gòu),如下圖所示:
2.1 Java NIO Channel 的祖先類(lèi)
java.nio.channels.Channel 是一個(gè) Java 接口,是整個(gè) Channel 家族的祖先,聲明的接口如下:
// 獲取 Channel 的狀態(tài)
public boolean isOpen();
// 關(guān)閉 channel
public void close() throws IOException;
Java NIO Channel 具有以下特性:
- Java NIO 支持面向字節(jié)流的數(shù)據(jù)讀寫(xiě)方式,數(shù)據(jù)從 ByteBuffer 讀取后寫(xiě)到 Channel 中,或者從 Channel 中讀取后寫(xiě)入 ByteBuffer 中。
- Java NIO Channel 支持字節(jié)流讀寫(xiě)雙向操作,一個(gè)處于 Open 狀態(tài)的 Channel,既可以進(jìn)行讀操作、也可以進(jìn)行寫(xiě)操作。
- Java NIO Channel 支持阻塞和非阻塞兩種模式。
- Java NIO Channel 是線程安全的。
2.2 Java NIO 中其他 Channel 接口
在 Java NIO Channel 體系中,對(duì)于 Socket 的抽象、數(shù)據(jù)讀、數(shù)據(jù)寫(xiě)、數(shù)組形式的多緩沖讀、數(shù)組形式的多緩沖寫(xiě)等功能分別進(jìn)行了抽象,每一個(gè)功能都對(duì)應(yīng)一個(gè) Java 接口,下來(lái)我們分別做一個(gè)說(shuō)明。
java.nio.channels.ReadableByteChannel 是一個(gè) Java Interface,是對(duì) Channel 讀操作的抽象,聲明的接口如下:
public int read(ByteBuffer dst) throws IOException;
Channel 的 read 方法是從 I/O 設(shè)備讀取數(shù)據(jù),保存在 ByteBuffer 中,為此調(diào)用者必須提供 ByteBuffer 用以保存數(shù)據(jù)。返回值是讀取的字節(jié)數(shù)、0、或者 -1。如果是阻塞式 Channel,read 至少返回 1 或者 -1;如果是非阻塞式 Chanel,read 可能會(huì)返回 0。
java.nio.channels.WritableByteChannel 是一個(gè) Java Interface,是對(duì) Channel 寫(xiě)操作的抽象,聲明的接口如下:
public int write(ByteBuffer src) throws IOException;
Channel 的 write 方法是從 ByteBuffer 讀取數(shù)據(jù),寫(xiě)入 I/O 設(shè)備中,為此調(diào)用者必須將要寫(xiě)出去的數(shù)據(jù)保存到 ByteBuffer 中。返回值是寫(xiě)入的字節(jié)數(shù)、0、或者 -1。如果是阻塞式 Channel,write 返回請(qǐng)求寫(xiě)入的字節(jié)數(shù) 或者 -1;如果是非阻塞式 write 可能會(huì)返回 0。
java.nio.channels.GatheringByteChannel 是一個(gè) Java Interface,是對(duì) Channel 寫(xiě)操作的抽象,可以寫(xiě)入一個(gè)數(shù)組,支持對(duì)多個(gè) ByteBuffer 的寫(xiě)入,聲明的接口如下:
public long write(ByteBuffer[] srcs, int offset, int length)
throws IOException;
public long write(ByteBuffer[] srcs) throws IOException;
java.nio.channels.ScatteringByteChannel 是一個(gè) Java Interface,是對(duì) Channel 讀操作的抽象,可以讀入一個(gè)數(shù)組,支持對(duì)多個(gè) ByteBuffer 的讀入,聲明的接口如下:
public long read(ByteBuffer[] dsts, int offset, int length)
throws IOException;
public long read(ByteBuffer[] dsts) throws IOException;
java.nio.channels.NetworkChannel 是一個(gè) Java Interface,表示一個(gè) Socket。實(shí)現(xiàn)此接口的 Channel 表示對(duì) Socket 的封裝。
java.nio.channels.SelectableChannel 是一個(gè) Java Interface,用于和 java.nio.channels.Selector 集成。聲明的主要接口如下:
public abstract SelectableChannel configureBlocking(boolean block)
throws IOException;
public final SelectionKey register(Selector sel, int ops)
throws ClosedChannelException
實(shí)現(xiàn)了 SelectableChannel 接口的類(lèi),可以將 I/O 操作設(shè)置為非阻塞模式,同時(shí)可以注冊(cè)到 Selector,通過(guò) I/O 多路復(fù)用機(jī)制監(jiān)聽(tīng)事件。
java.nio.channels.ByteChannel 也是一個(gè) Java 接口,只是實(shí)現(xiàn)了 java.nio.channels.ReadableByteChannel 和 java.nio.channels.WritableByteChannel 兩個(gè)接口。ByteChannel 本身沒(méi)有聲明任何接口,實(shí)現(xiàn)了讀寫(xiě)聚合的功能。
3. Channel 實(shí)現(xiàn)類(lèi)
在 Channel 的類(lèi)的層次結(jié)構(gòu)圖中,我們畫(huà)出四個(gè)常用的實(shí)現(xiàn)類(lèi)如下:
-
FileChannel
文件 Channel 類(lèi)是對(duì)磁盤(pán)文件的抽象,可以讀寫(xiě)磁盤(pán)文件數(shù)據(jù)。需要通過(guò) FileInputStream 的 getChannel 方法創(chuàng)建 FileChannel 的對(duì)象,你不可以直接創(chuàng)建 FileChannel 的對(duì)象。FileChannel 對(duì)象的創(chuàng)建方法如下:
FileInputStream inFile = new FileInputStream("D:\\fileChannelTest.txt");
ReadableByteChannel fileChannel = inFile.getChannel();
-
DatagramChannel
數(shù)據(jù)報(bào) Channel 是用于抽象 UDP Socket,可以將 UDP 數(shù)據(jù)的讀寫(xiě)集成到 Selector 機(jī)制中。DatagramChannel 對(duì)象的創(chuàng)建方法:
DatagramChannel ch = DatagramChannel.open();
-
SocketChannel 是對(duì) TCP 的抽象,用于讀寫(xiě) TCP 數(shù)據(jù),用于 TCP 客戶端和服務(wù)器端
-
ServerSocketChannel 是對(duì) TCP 監(jiān)聽(tīng) Socket 的抽象,用于 TCP 服務(wù)器的創(chuàng)建。
4. 總結(jié)
本節(jié)重點(diǎn)是對(duì) Java NIO Channel 類(lèi)的體系結(jié)構(gòu)進(jìn)行了介紹。通過(guò) channel 類(lèi)的體系結(jié)構(gòu)圖,我們可以看出,主要是對(duì)數(shù)據(jù)讀、數(shù)據(jù)寫(xiě)、數(shù)組形式的多緩沖讀、數(shù)組形式的多緩沖寫(xiě)、以及和多路復(fù)用機(jī)制 Selector的集成等功能的抽象。每一個(gè)接口都對(duì)應(yīng)了相應(yīng)功能的實(shí)現(xiàn),將這些接口進(jìn)行一個(gè)組合,就是一個(gè)具體的 I/O 功能的實(shí)現(xiàn),這一具體的組合是通過(guò)具體實(shí)現(xiàn)類(lèi)來(lái)體現(xiàn)的。比如 ,F(xiàn)ileChannel 是對(duì)磁盤(pán)文件的抽象,DatagramChannel 是對(duì) UDP Socket 的抽象,Socket Channel 是對(duì) TCP Socket 的抽象,ServerSocketChannel 是對(duì) TCP Server 監(jiān)聽(tīng)邏輯的抽象。
通過(guò)逐個(gè)分析每一個(gè)接口所提供的能力,我們就熟悉了完整的 Java NIO Channel 體系。