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程式碼,這裡就不坐介紹了。
地址:https://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();
}
}
}
}
接收端接收資料並退出:
以上即為本文理論知識+程式碼實戰全部內容。如有錯誤歡迎指正。
本文參考文章: