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:第一個步驟無序等待,但是會一直詢問內核是否準備好。(BIO的多執行緒版)
-
- 多路復用I/O:在非阻塞的前提下優化,不會一直詢問。但是多一個select期。(NIO)
- 適用於連接數目多且連接比較短的操作(輕操作)。比如rpc框架,聊天伺服器等。
- 多路復用I/O:在非阻塞的前提下優化,不會一直詢問。但是多一個select期。(NIO)
非同步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)
- 按照數據流的流向不同:輸入流,輸出流
- 按照流的角色分為:
- 節點流:直接從數據源或目的地讀寫數據
-
處理流:不直接連接到數據源或目的地,而是連接在已存在的流(節點流或處理流)之上,通過對數據的處理為程式提 供更為強大的讀寫功能。
實現類:
寄語:我努力奔跑是為了追上那個曾經被寄予厚望的自己