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接口:
image

新一代DMA授權處理IO接口:
image

通道(Channel)模式:
image

通道的產生是由於操作系統的升級而支持的。

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來接收數據、傳輸數據。
image

分散讀取、聚集寫入代碼示例:

    @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();
                }
            }
        }
    }

接收端接收數據並退出:
image


以上即爲本文理論知識+代碼實戰全部內容。如有錯誤歡迎指正。

本文參考文章:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章