Java-NIO之Channel(通道)
1:Channel是什麼
通道表示與實體的開放連接,例如硬體設備、文件、網路套接字或能夠執行一個或多個不同 I/O 操作(例如讀取或寫入)的程式組件。
1.1:Channel與Stream的對比
Stream | Channel | 為什麼 | |
---|---|---|---|
是否支援非同步 | 否 | 是 | |
是否同時支援輸入和輸出 | 否 | 是 | Stream的輸入、輸出分別需要InputStream、OutputStream |
是否必須結合Buffer使用 | 否 | 是 | 緩衝區是通道內部發送數據和接收數據的端點 |
性能 | 低 | 高 | 通道是訪問IO服務的導管,通過通道,我們可以以最小的開銷來訪問作業系統的I/O服務 |
1.2:Channel的類型
文件類:
- FileChannel
可通過 FileInputStream/FileOutputStream 的getChannel方法獲取通道。
網路類:
基於socket協議:
- SocketChannel
- ServerSocketChannel
可通過 Socket/SocketServer 的getChannel方法獲取通道。
基於UDP協議:
- DatagramChannel
可通過 DatagramSocket 的getChannel方法獲取通道。
1.3:作業系統IO演變史
早一代IO操作是由CPU負責IO介面:
新一代DMA授權處理IO介面:
通道(Channel)模式:
通道的產生是由於作業系統的升級而支援的。
2:Channel和作業系統的關係
在作業系統中對IO設備的控制方式一共有四種,按時間線依次是 輪詢、中斷、DMA、和通道 方式。
- 輪旋
輪詢就是進行IO時作業系統一直問控制器數據準備好了沒有。
- 中斷
中斷就是非同步的方式進行了,CPU向設備控制器發送一條IO指令後接著返回繼續做原來的工作,而當設備控制器從設備中取出數據放到控制器的暫存器中後便向CPU發送中斷訊號,CPU在檢查完數據後便向控制器發送取走數據的訊號,將數據寫入記憶體,但仍是以位元組為單位的。
- DMA
DMA則是CPU和設備控制器之間的引入的一層加快速度的手段,由DMA代替CPU進行數據傳送,CPU將指令發送給DMA,DMA向控制器發送請求,設備控制器將數據從緩衝區將數據直接寫入記憶體。完成後設備控制器發送一個訊號給DMA,DMA重複檢查數據是否傳送完成,確認完成後中斷讓CPU知道。
DMA比起中斷方式已經顯著減少了CPU的干預,但是CPU每發出一條IO指令,只能去讀寫一個連續的數據塊,當要讀多個數據塊並存放到不同的記憶體區域中去,CPU需要發送多條IO指令及進行多次中斷。
- 通道
IO通道方式是DMA方式的發展,把對一個數據塊的干預減少為對一組數據塊的干預。
IO通道有三種:
- 位元組多路通道(Byte Multiplexor Channel)
- 選擇通道(Block Selector Channel)
- 數組多路通道(Block Multiplexor Channel)
根據通道的工作方式分類,通道可以分為位元組多路通道、選擇通道、數組多路通道。
位元組多路通道是一種簡單的共享通道,主要用於連接大量的低速設備。
由於外圍設備的工作速度較慢,通道在傳送兩個位元組之間有很多空閑的時間,利用這段空閑時間位元組多路通道可以為其他外圍設備服務。因此位元組多路通道採用分時工作方式,依賴它與CPU之間的高速匯流排分時為多台外圍設備服務。
數據選擇通道用於連接高速的外圍設備。
高速外圍設備需要很高的數據傳輸率,因此不能採用位元組多路通道那樣的控制方式。選擇通道在物理上可以連接多台外圍設備,但多台設備不能同事工作。也就是在同一段時間內,選擇通道只能為一台外圍設備服務,在不同的時間內可以選擇不同的外圍設備。一旦選中某一設備,通道就進入忙狀態,知道該設備數據傳輸工作結束,才能為其他設備服務。
數組多路通道是位元組多路通道和選擇通道的結合。
其基本思想是:當某設備進行數據傳輸時,通道只為該設備服務;當設備在進行定址等控制性操作時,通道暫時斷開與設備的連接,掛起該設備的通道程式,去為其他設備服務,即執行其他設備的通道程式。有數數組多路通道既保持了選擇通道的告訴傳輸數據的有點,又充分利用了控制性操作偶讀時間間隔為其他設備服務,使得通道效率充分得到發揮,因此數據多路通道在實際電腦系統中應用最多,適合於高速設備的數據傳輸。(以上引用內容來源於百度教育)
至於JAVA的Channel和作業系統的的通道是如何選擇通道類型、如何交互的就沒法深入了,暫且理解JAVA的Channel是對作業系統的通道的一種抽象實現吧。
3:Channel文件通道
上一篇已經介紹過Channel的文件記憶體映射(map),就不做介紹了。
所謂的分散讀取、聚集寫入就是用多個buffer來接收數據、傳輸數據。
分散讀取、聚集寫入程式碼示例:
@Test
public void gatherWrite() {
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
File file = new File("src/test/java/com/loper/mine/SQLParserTest.java");
inputStream = new FileInputStream(file);
inChannel = inputStream.getChannel();
ByteBuffer buffer1 = ByteBuffer.allocate(8);
ByteBuffer buffer2 = ByteBuffer.allocate(15);
ByteBuffer[] buffers = new ByteBuffer[]{buffer1, buffer2};
// 分散讀取
inChannel.read(buffers);
for (ByteBuffer buffer : buffers) {
buffer.flip();
System.out.println(buffer.mark());
}
File outFile = new File("src/test/java/com/loper/mine/1.txt");
outputStream = new FileOutputStream(outFile);
outChannel = outputStream.getChannel();
// 聚集寫入
outChannel.write(buffers);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null)
inputStream.close();
if (outputStream != null)
outputStream.close();
if (inChannel != null)
inChannel.close();
if (outChannel != null)
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4:Channel網路通道
4.1:socket協議
這部分程式碼比較複雜,可以翻看我的github程式碼,這裡就不坐介紹了。
地址://github.com/zgq7/devloper-mine/tree/master/src/main/java/com/loper/mine/core/socket/nio
4.2:UDP協議
UDP發送數據:
@Test
public void send() {
DatagramChannel channel = null;
try {
channel = DatagramChannel.open();
// 設置為非阻塞
channel.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(1024);
Scanner scanner = new Scanner(System.in);
while (true) {
String nextLine = scanner.nextLine();
buffer.put(nextLine.getBytes());
buffer.flip();
channel.send(buffer, new InetSocketAddress("127.0.0.1", 8056));
buffer.clear();
if ("over".equals(nextLine))
break;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (channel != null) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
UDP接收數據:
@Test
public void receive() {
DatagramChannel channel = null;
try {
channel = DatagramChannel.open();
// 設置為非阻塞
channel.configureBlocking(false);
channel.bind(new InetSocketAddress(8056));
Selector selector = Selector.open();
channel.register(selector, SelectionKey.OP_READ);
while (true) {
int select = selector.select();
boolean exit = false;
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isReadable()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.receive(buffer);
buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
String str = new String(data);
System.out.println("收到:" + str);
if ("over".equals(str))
exit = true;
}
iterator.remove();
}
if (exit)
break;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (channel != null) {
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
接收端接收數據並退出:
以上即為本文理論知識+程式碼實戰全部內容。如有錯誤歡迎指正。
本文參考文章: