IO多路復用

先說明一個問題:在Java API中提供了兩套NIO,一套是針對標準輸入輸出NIO,另一套就是網絡編程NIO。網絡編程其實就是多了一個連接的過程,常用在Netty一些框架。本文主要講述標準輸入輸出NIO

一.BIO,NIO,AIO的區別

先說一些IO的模式

  • 我們發現,IO不是我們想像中那麼簡單。要想進行IO操作,需要發一個請求給CPU,CPU得到通知後,此時CPU就需要調用操作系統內核程序(磁盤控制器)。這就是用戶態->內核態
  • 磁盤控制器接到通知,使用DMA技術將數據放到PageCache中,再由CPU把數據從內核程序傳回用戶程序(buffer)。這就是內核態->用戶態
  • 關於上圖的操作系統知識,如果不是清楚,請先移步//www.cnblogs.com/monkey-xuan/category/2067966.html,分3章講清楚操作系統核心內容。

根據上面的模型,其實我們可以把一次IO粗略的分為2個步驟:「內核數據準備好」和「數據從內核態拷貝到用戶態

  • 同步IO:
    • 阻塞I/O兩個步驟都要等待。(BIO)
      • 適用於連接數目比較小,且固定的架構

 

    • 非阻塞I/O第一個步驟無序等待,但是會一直詢問內核是否準備好。(BIO的多線程版)

 

    • 多路復用I/O在非阻塞的前提下優化,不會一直詢問。但是多一個select期。(NIO)
      • 適用於連接數目多且連接比較短的操作(輕操作)。比如rpc框架,聊天服務器等。
  • 異步I/O: 兩個步驟都無需等待,詢問完交給內核自動完成。(AIO)
    • 適用於連接數目多且連接比較長的操作(重操作),比如相框服務器,充分調用OS參與並發操作。

 

多路復用的系統方法說明:一直在升級

  • select:用一個線程,接收到IO的請求,就將這些進行IO的文件描述符複製一份到內核,然後內存進行遍歷,看哪個文件已經準備好數據,將準備好的文件描述符的個數返回給用戶態,用戶態再進行遍歷,找到是哪個文件描述符的IO。有3個缺點如下
    • select 調用需要傳入 fd 數組,需要拷貝一份到內核,高並發場景下這樣的拷貝消耗的資源是驚人的。(可優化為不複製)
    • select 在內核層仍然是通過遍歷的方式檢查文件描述符的就緒狀態,是個同步過程,只不過無系統調用切換上下文的開銷。(內核層可優化為異步事件通知)
    • select 僅僅返回可讀文件描述符的個數,具體哪個可讀還是要用戶自己遍歷。(可優化為只返回給用戶就緒的文件描述符,無需用戶做無效的遍歷)
  • poll:它和 select 的主要區別就是,去掉了 select 只能監聽 1024 個文件描述符的限制。
  • epoll:epoll 是最終的大 boss,它解決了 select 和 poll 的一些問題。
    • 內核中保存一份文件描述符集合,無需用戶每次都重新傳入,只需告訴內核修改的部分即可。
    • 內核不再通過輪詢的方式找到就緒的文件描述符,而是通過異步 IO 事件喚醒。
    • 內核僅會將有 IO 事件的文件描述符返回給用戶,用戶也無需遍歷整個文件描述符集合。

這裡附上一個大佬寫的關於IO多路復用的動畫:IO多路復用

 二.NIO的組成

Channel:

  • 數據傳輸的雙向通道,既可以用來讀數據又可以用來寫數據。主要實現類如下:
    • FileChannel:文件IO
    • DatagramChannel:UDP的IO
    • SocketChannel 和 ServerSocketChannel:TCP的IO

Buffer:

  • 用於和 NIO 通道進行交互。
  • Buffer 是一個頂層父類,它是一個抽象類,常用的 Buffer 的子類有:ByteBuffer、 IntBuffer、 CharBuffer、 LongBuffer、 DoubleBuffer、 FloatBuffer、ShortBuffer等
  • 下圖為網絡IO的一個簡單圖

 

Selector:

  • 選擇器 ,也可以翻譯為 多路復用器 。它是 Java NIO 核心組件中的一個,用於檢查一個或多個 NIO Channel(通道)的狀態是否處於可讀、可寫。如此可以實現單線程管理多個 channels,也就是可以管理多個網絡鏈接,就可以避免線程上下文切換。一個Selector對應一個線程,

三.Buffer中對應的Position, Mark, Capacity,Limit都啥?

  • capacity:緩衝區容量的大小,就是裏面包含的數據大小。
  • limit:對buffer緩衝區使用的一個限制,從這個index開始就不能讀取數據了。
  • position:代表着數組中可以開始讀寫的index, 不能大於limit。
  • mark:是類似路標的東西,在某個position的時候,設置一下mark,此時就可以設置一個標記
    • 後續調用reset()方法可以把position複位到當時設置的那個mark上。去把position或limit調整為小於mark的值時,就丟棄這個mark,如果使用的是Direct模式創建的Buffer的話,就會減少中間緩衝直接使用DirectorBuffer來進行數據的存儲。

四.Java中的IO流(BIO):

分類

  • 按照操作數據單位不同:位元組流(8bit),字符流(16bit)
  • 按照數據流的流向不同:輸入流,輸出流
  • 按照流的角色分為:
    • 節點流:直接從數據源或目的地讀寫數據
    • 處理流:不直接連接到數據源或目的地,而是連接在已存在的流(節點流或處理流)之上,通過對數據的處理為程序提 供更為強大的讀寫功能。

 

 實現類:

 

 

寄語:我努力奔跑是為了追上那個曾經被寄予厚望的自己