NIO/ZeroCopy

1、傳統IO:面向流

  • 字節流和字符流:
    • 字節流:以字節爲單位,每次次讀入或讀出是8位數據。能夠讀任何類型數據。
    • 字符流:以字符爲單位,每次次讀入或讀出是16位數據。其只能讀取字符類型數據。
  • 輸出流和輸入流:
    • 輸出流:從內存讀出到文件。只能進行寫操做。
    • 輸入流:從文件讀入到內存。只能進行讀操做。
  • 節點流和處理流:
    • 節點流:直接與數據源相連,讀入或讀出。
    • 處理流:與節點流一塊使用,在節點流的基礎上,再套接一層,套接在節點流上的就是處理流。

2、BIO

BIO就是傳統的socket編程。每有一個客戶端連入,服務端就須要另起線程爲其服務,很容易形成資源枯竭。html

3、NIO:面向緩衝區

Channel管道比做成鐵路,buffer緩衝區比做成火車(運載着貨物)java

  • buffer演示:

public static void main(String[] args) {

        // 建立一個緩衝區
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        // 添加一些數據到緩衝區中
        String s = "Java3y";
        byteBuffer.put(s.getBytes());
        //切換成讀模式
        byteBuffer.flip();
        // 建立一個limit()大小的字節數組(由於就只有limit這麼多個數據可讀)
        byte[] bytes = new byte[byteBuffer.limit()];
        // 將讀取的數據裝進咱們的字節數組中
        byteBuffer.get(bytes);
        // 輸出數據
        System.out.println(new String(bytes, 0, bytes.length));
}

 

  • channel演示

// 1. 經過本地IO的方式來獲取通道
        FileInputStream fileInputStream = new FileInputStream("F:\\3yBlog\\JavaEE經常使用框架\\Elasticsearch就是這麼簡單.md");

        // 獲得文件的輸入通道
        FileChannel inchannel = fileInputStream.getChannel();

        // 2. jdk1.7後經過靜態方法.open()獲取通道
        FileChannel.open(Paths.get("F:\\3yBlog\\JavaEE經常使用框架\\Elasticsearch就是這麼簡單2.md"), StandardOpenOption.WRITE);

    複製文件編程

    

    使用內存映射文件的方式實現文件複製的功能(直接操做緩衝區):設計模式

    

    通道之間經過transfer()實現數據的傳輸(直接操做緩衝區):數組

    

 

  • 網絡IO模型

阻塞I/O:網絡

非阻塞I/O:併發

I/O多路複用:框架

在Linux下對文件的操做是利用文件描述符(file descriptor)來實現的socket

在Linux下它是這樣子實現I/O複用模型的:調用select/poll/epoll/pselect其中一個函數,傳入多個文件描述符,若是有一個文件描述符就緒,則返回,不然阻塞直到超時。函數

  1. 當用戶進程調用了select,那麼整個進程會被block;
  2. 而同時,kernel會「監視」全部select負責的socket;
  3. 當任何一個socket中的數據準備好了,select就會返回;
  4. 這個時候用戶進程再調用read操做,將數據從kernel拷貝到用戶進程(空間)。

因此,I/O 多路複用的特色是經過一種機制一個進程能同時等待多個文件描述符,而這些文件描述符其中的任意一個進入讀就緒狀態,select()函數就能夠返回。select/epoll的優點並非對於單個鏈接能處理得更快,而是在於能處理更多的鏈接

IO多路複用對應的設計模式:Reactor模式

http://www.javashuo.com/article/p-yeqleamk-kt.html

 

  • 零拷貝

1、普通拷貝

    

    

read(file, tmp_buf, len);
write(socket, tmp_buf, len);
  1. 程序使用read()系統調用,系統由用戶態轉換爲內核態,磁盤中的數據由DMA(Direct memory access)的方式讀取到內核讀緩衝區(kernel buffer)。DMA過程當中CPU不須要參與數據的讀寫,而是DMA處理器直接將硬盤數據經過總線傳輸到內存中。
  2. 系統由內核態轉爲用戶態,當程序要讀的數據已經徹底存入內核讀緩衝區之後,**程序會將數據由內核讀緩衝區,寫入到用戶緩衝區,**這個過程須要CPU參與數據的讀寫。
  3. 程序使用write()系統調用,系統由用戶態切換到內核態,數據從用戶緩衝區寫入到網絡緩衝區(Socket Buffer),這個過程須要CPU參與數據的讀寫。
  4. 系統由內核態切換到用戶態,網絡緩衝區的數據經過DMA的方式傳輸到網卡的驅動(存儲緩衝區)中(protocol engine)

能夠看到,普通的拷貝過程經歷了四次內核態和用戶態的切換(上下文切換),兩次CPU從內存中進行數據的讀寫過程,這種拷貝過程相對來講比較消耗系統資源。

2、零拷貝

內存映射方式(NIO中的map()就是用的這種):

tmp_buf = mmap(file, len);
write(socket, tmp_buf, len);
  1. mmap()系統調用首先會使用DMA的方式將磁盤數據讀取到內核緩衝區,而後經過內存映射的方式,使用戶緩衝區和內核讀緩衝區的內存地址爲同一內存地址,也就是說不須要CPU再講數據從內核讀緩衝區複製到用戶緩衝區。
  2. 當使用write()系統調用的時候,cpu將內核緩衝區(等同於用戶緩衝區)的數據直接寫入到網絡發送緩衝區(socket buffer),而後經過DMA的方式將數據傳入到網卡驅動程序中準備發送。

能夠看到這種內存映射的方式減小了CPU的讀寫次數,可是用戶態到內核態的切換(上下文切換)依舊有四次(調用map()是兩次,調用write()是兩次),同時須要注意在進行這種內存映射的時候,有可能會出現併發線程操做同一塊內存區域而致使的嚴重的數據不一致問題,因此須要進行合理的併發編程來解決這些問題。

sendfile()方式(NIO中的transfer()就是用的這種):

sendfile(socket, file, len);
  1. sendfile()系統調用也會引發用戶態到內核態的切換,與內存映射方式不一樣的是,用戶空間此時是沒法看到或修改數據內容,也就是說這是一次徹底意義上的數據傳輸過程。
  2. 從磁盤讀取到內存是DMA的方式,從內核讀緩衝區讀取到網絡發送緩衝區,依舊須要CPU參與拷貝,而從網絡發送緩衝區到網卡中的緩衝區依舊是DMA方式。

依舊有一次CPU進行數據拷貝,兩次用戶態和內核態的切換操做,相比較於內存映射的方式有了很大的進步,但問題是程序不能對數據進行修改,而只是單純地進行了一次數據的傳輸過程。

4、select,poll,epoll

http://www.javashuo.com/article/p-cntwfxgr-eb.html

===========================

NIO:

https://juejin.im/post/5af942c6f265da0b7026050c

ZeroCopy:

https://blog.csdn.net/cringkong/article/details/80274148

https://www.jianshu.com/p/8c6b056f73ce

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息