划分內核態/用戶態
之前說過七層/五層/四層的網絡模型,我們從網絡模型可以看出傳輸層(tcp/udp)開始 就是我們平常編寫程序所運行的層次了。在系統層級,為了系統安全之類的考慮我們將 傳輸層向上 划分為用戶態 將 傳輸層向下 划分到 內核態(暫時可以認為這么划分)

客戶端-服務端
在網絡交互中客戶端和服務端的交互時發生了什么?
- 首先我們
應用啟動運行,對外暴露一個端口(或者多個),此時調用系統``創建一個(或多個)這個端口的socket(或者說是創建一個(或多個)監聽器) 客戶端發起請求,此時客戶端生成一個socket然后通過傳輸層->網絡層->鏈路層->物理層 ->( 物理層-鏈路層-網絡層-傳輸層)``服務器進行三次握手確認鏈接- 然后
客戶端將數據按照第二部的鏈路順序 在發送到 服務端 服務端從網卡->將數據(0/1)讀出->內核態->用戶態- 用戶態處理數據,將處理后的數據再原路返回。

從上我們可以知道 客戶端 和 服務端 的數據``流向。這僅僅是一台客戶端的,作為服務器肯定是要有多台客戶端進行通信的,如果有多個客戶端同時訪問此時的過程如何呢?這就引出我們今天要說的主題:IO多路復用。為了講清楚,我們先將傳統的網絡io拉出來進行一步步推導。
我們在上面說過,服務端應用啟動的時候會創建一個主動socket(也就是監聽器),那么如果有客戶端建立鏈接的時候被監聽到,然后執行 創建一個被動socket執行服務端的代碼:服務端一般就是讀取數據 然后 處理數據 最后返回數據 關閉鏈接
但是 我們建立鏈接的時候 數據還沒有``到達``用戶態,也就是此時數據不一定傳輸完成了。那么我們服務端的讀數據 也就被阻塞了(我們程序發起io調用,如果內核態 沒有准備好,那么我們程序是在io 階段被阻塞的,也就是我們平常說的系統卡了)。此時就引出我們第一個概念:阻塞IO
關於數據的阻塞



Read 過程
在上面我們可以看到 客戶端寫入流 和 服務端讀入流 是有一個阻塞的階段的(客戶端可以分多次寫入流,然后發送到服務端),而且這里我們要注意的是,這個流是從物理層傳入的(服務端舉例子,客戶端是相反的),那么數據到達用戶層 還是有一個 內核態 到用戶態的切換(這個上下文切換是比較耗費性能的)。
然后我們對從 底層 到 用戶層的過程進行一下分析:將read (系統提供的read 函數)展開來,可以發現這個read 分成兩個部分:
數據從外部流入網卡然后走到內核緩沖區,此時客戶端socke文件描述符變成1,然后用戶緩沖區再去讀取(服務端進行讀取使用)


一、阻塞IO
在這個模型中,服務端處理請求是串聯的。也就是說如果這個請求被阻塞了,那么剩下的請求都要被阻塞``等待``上一個請求處理完成才行。所以,我們上面說,在 服務器讀數據的時候,數據還沒到(數據還沒讀到用戶態),那么服務器被阻塞,然后其他客戶端的請求也不能被處理。
比如:
小明和小紅兩個人訪問同一個服務,然后小明先點,但是數據沒被處理完成,然后小紅在進行發送請求,此時服務器就將小紅的請求掛起,等待小明的處理完成在進行處理。
這樣來說,服務器的cpu豈不是會浪費?當用戶數量少的時候還可以,但是如果用戶數量多來怎么行
所以我們就自己優化一下。
優化阻塞io
怎么優化,既然服務器此時還在等待數據,那么我們在開一個線程去處理另外的客戶端不就ok了?
所以我們對監聽和 read進行解耦合,監聽到一個客戶端就放進來一個客戶端的請求,然后服務再啟動一個線程去處理這個請求。
但是這個有兩個比較突出的問題:
- 服務端需要開辟
大量的線程,這對服務端的壓力是很大的 - 這個read 還是
單線程``阻塞的,我們沒辦法向下走啊
所以這個對於傳統的io 來說還是沒有解決實際的問題,想要解決只能在操作系統中(內核態)處理。而這就引出我們第二個概念:非阻塞io
二、非阻塞IO
我們在看上面的read 函數,可以發現read 函數是分成兩個部分進行的,那我們是不是可以將這個兩個過程分開?
服務端的read 執行,然后read 直接返回-1 讓 服務端 代碼進行下一步操作,不用在阻塞到讀取這里。

雖然系統不再阻塞服務端的讀取程序了,但是服務端還是要使用這個數據啊,所以服務端還是需要有個線程不斷的進行循環,以此知道數據讀取完成了,所以還是有服務端創建線程的壓力啊。(也就是我們還是需要自己循環這個狀態;還有一點 read 讀取 還是 阻塞的,我們非阻塞的只是數據預處理階段-也就是網卡到內核緩沖區的部分,這個是同步和異步的一個重要區分點)

優化
我們再一次發揮聰明的頭腦,既然服務端為每個客戶端創建一個線程是耗費創建線程的壓力,那么就將每個客戶端的文件描述符存儲起來(數組),然后等到可以在用戶態read 的時候在調用服務端的注冊函數不就ok了,然后單獨創建一個線程 專門用來做 遍歷。這樣不就減少了服務端的壓力了


這是不是有點多路復用的意思?
但是我們在應用層寫的read 還是要調用系統的read 方法,也就是還是需要消耗系統資源的(在 while 循環里做系統調用,就好比你做分布式項目時在 while 里做 rpc 請求一樣,是不划算的)。所以我們能不能扔到系統中去?這就引出我們今天的角-io多路復用
三、IO多路復用
多路復用的思想: 是 在 非阻塞 io 的基礎上進行優化的,也就是對於 read 第一部分 預處理階段 是非阻塞的。(可以理解為,我們告訴系統那些在等待,等系統處理好了 在通知 系統,我們再去調用io 讀取)
select
此時操作系統提供了一個select 函數,我們可以通過它把一個文件描述符的數組發給操作系統, 讓操作系統去遍歷,確定哪個文件描述符可以讀寫, 然后告訴我們去處理:

這里注意一下,雖然我們讓系統遍歷了,但是我們自己還是需要遍歷的,只不過此時我們自己遍歷的沒有了系統的開銷了。然后有了數據之后我們在進行調用注冊函數。

但是我們知道
- 系統調用fd 數據,也就是拷貝一份到內核,高並發場景下這樣的拷貝消耗的資源是驚人的。(可優化為不復制)且數組也是有限制的。
- 還有select 在內核層仍然是通過遍歷的方式檢查文件描述符的就緒狀態,是個同步過程,只不過無系統調用切換上下文的開銷。(內核層可優化為異步事件通知)
- select 僅僅返回可讀文件描述符的個數,具體哪個可讀還是要用戶自己遍歷。(可優化為只返回給用戶就緒的文件描述符,無需用戶做無效的遍歷)
這一點我們還有一個注意的點:我們read第二步還是在阻塞的
poll
為了解決數組的限制(這不阻礙高並發的數量么),所以它用了動態數組,也就是鏈表,去掉了 select 只能監聽 1024 個文件描述符的限制。
epoll
此時我們的終極解決方案過來了
epoll 主要就是針對這三點進行了改進。

- 內核中保存一份文件描述符集合,無需用戶每次都重新傳入,只需告訴內核修改的部分即可。
- 內核不再通過輪詢的方式找到就緒的文件描述符,而是通過異步 IO 事件喚醒。
- 內核僅會將有 IO 事件的文件描述符返回給用戶,用戶也無需遍歷整個文件描述符集合。
這里我們就將linux中的io 多路復用講完了。

四、信號驅動IO
我們在io多路復用,到最后我們的epoll 中,可以看到,最后是內核態 將准備好的io 給到 應用層的 程序,所以我們可以進一步來進行一下優化,我們在程序層 將 數據准備 和io讀取 進行分開:
也就是 在主線中調用 數據預處理 等方法,然后另寫 一個方法對 預處理完成 之后的方法進行 處理。也就是在程序層我們做一個“異步” (注意 ,這里其實還是同步的,因為我們的read 第二部分還是阻塞的,也就是我們還是在等待這個read ,可以理解為:我們不在主線程等待了,對於內核態來說並不知道,認為 用戶態的這段還是在一個線程中)
五、異步IO(AIO)
我們上面做了那么多, 我們在應用層做的都是想要 在內核態數據真正 讀取到用戶態 的時候才使用數據,所以 我們考慮一下系統 對於第二部分也進行一個非阻塞的 返回 不就ok 了。
也就是 服務端(用戶態)進行一次系統調用(一次上下文切換),然后就往下進行,然后內核態 完成 用戶態的 拷貝的時候在進行通知,處理。
總結
注意一下,本章重點想要說的是 io 多路復用,其他都是用來和 多路復用進行輔助理解的。
- 阻塞io 就是 服務端 從建立鏈接 ->讀取數據->處理數據 都是一個線程中完成,一次只處理一個;
- 我們通過 創建多線程 來解決 防止 主線程 卡主 或者其他線程等待的時間太長的問題
- 非阻塞io 出來之后,我們就可以將 監聽 和 讀取 在操作系統層面解 耦合,但是我們還是需要自己遍歷狀態
- select 函數出來之后,我們可以將數組放到用戶態進行處理(還是需要自己遍歷,只不過沒有系統開銷了)
- poll 使用動態數組來儲存 描述符,解決數組長度問題(還是需要自己遍歷,只不過沒有系統開銷了)
- epoll 不用每次都傳入 描述符,然后使用紅黑樹 提高系統的性能,這下 我們應用層 終於不用在寫遍歷去處理了。
- 型號驅動io 是 程序層結偶,等待 內核台可以讀取的時候,在進行io 調用
- 異步io(AIO) 是 內核態 進行解耦 ,也就是我們程序層 一次調用,然后內核態 到用戶層的拷貝 完成的時候 我們的程序層的io 調用就被執行了,不用再去程序層 另外寫東西執行了。

最后說一下,這些都是我自己的理解,如果內容有誤,請聯系告知,在此不勝感激!!!
本文的內容都是使用下面的博客進行理解自己修改的:
【https://baijiahao.baidu.com/s?id=1718409483059542510&wfr=spider&for=pc】
【https://zhuanlan.zhihu.com/p/470778284】
部分圖片來源百度搜索
如有侵權請聯系我刪除,感謝!!!
