目錄
1.NAT技術
NAPT
2.NAT和代理服務器
3.網線通信各層協議總結
補充說明
4.五種 IO 模型
1.什么是IO?什么是高效的IO?
2.有那些IO的方式?這么多的方式,有那些是高效的?
異步 IO
🎣 關鍵缺陷類比
📝 具體技術缺陷
?? 實踐中的坑
多路復用,引入了對多個文件的同時等待,那么對這多個文件的管理,就又可以引入我們的數據結構啦
1.NAT技術
NAT技術背景
之前我們討論了, IPv4協議中, IP地址數量不充足的問題
NAT技術當前解決IP地址不夠用的主要手段, 是路由器的一個重要功能;
- NAT能夠將私有IP對外通信時轉為全局IP. 也就是就是一種將私有IP和全局IP相互轉化的技術方法:
- 很多學校, 家庭, 公司內部采用每個終端設置私有IP, 而在路由器或必要的服務器上設置全局IP;
- 全局IP要求唯一, 但是私有IP不需要; 在不同的局域網中出現相同的私有IP是完全不影響的;
NAT IP轉換過程
之前說過,當進行數據跨網絡傳輸時,構建一個報文向下交付,到網絡層后要經過路由選擇,發現要去目標主機和我并不再同一個子網,所以向下交付封裝MAC幀交付給出口路由器 ,家用路由器的IP地址肯定知道因為你連接過路由器,如果不知道MAC地址就做ARP。
- 然后出口路由器經過同樣的策略交給下一跳路由器直到交給目標主機。
- 在整個交付過程中,路由器不僅僅是把報文交給下一跳。
- 更重要的在交付的時候把源IP地址不斷的做替換。
- 每一個路由器不僅有LAN口IP,還要WAN口IP,當做數據包轉發時,是把源IP替換成路由器的WAN口IP。
一路替換然后數據包經過目的主機處理然后返回接下來怎么走呢?
下面我們具體說一下整個過程。
- 客戶端A、B、C都有自己的私有IP,每臺主機我們要透過現象看本質,每臺機器都有TCP/IP協議棧。
- 客戶端A將數據經過路由器發送給服務器。
- 其中路由器本質天然的就包含了NAT地址轉化的功能,所以在數據在剛開始轉發時源IP地址是它自己私有IP,然后經過路由選擇發現要去路由器,到路由器之后就把當前IP報頭中源地址替換成路由器自己的WAN口IP,然后讓路由替這個主機發起網絡請求,最終經過內網和公網轉發這個數據到了服務器。
- 服務器處理好了根據得到的源IP構建響應返回給NAT路由器,那此時NAT路由器中報文中目的IP目前是202.244.174.37,接下來問題是怎么從這個路由器回到主機。
有人可能說轉化時建立一個映射關系,然后回到NAT路由器在把目的IP轉化一次變成源IP地址不就可以了嗎,但如果是一個局域網主機都訪問同于一個服務器呢?
NAPT
那么問題來了, 如果局域網內, 有多個主機都訪問同一個外網服務器, 那么對于服務器返回的數據中, 目的IP都是相同的. 那么NAT路由器如何判定將這個數據包轉發給哪個局域網的主機?
- 這時候NAPT來解決這個問題了. 使用IP+port來建立這個關聯關系
- (還是 我們之前提到的,一層區分不了,那就再加一層😋
- 當數據包回到NAT路由器的時候,目前并不能確認這個報文應該轉給主機A、B、C當中哪一個。
- 但我們要求它必須分清楚,是誰的數據就給誰返回,就決定了當前路由器就必須自己維護從外網到內網之間數據包和主機之間的對應關系。
- 學了這么久實際上我們知道主機A、B、C三臺主機IP地址是不一樣的,所以對應每臺主機都會有特定的標識符來告訴NAT路由器我們各自是誰。
- 可是有一個細節我們從開始到現在一直沒說,實際上通信的并不全是客戶端A、B、C這些主機,其實本質是戶端A、B、C上特定的進程和服務器上某個特定的進程在通信,而進程都是自己的端口號的。并且同時可能會存在一臺主機上有不同進程可能都要去訪問外網。
所以NAT路由器除了要區分客戶端A、B、C之間差別,也要有能力區分同一臺主機不同的進程!
- 說白了就是NAT路由器不僅僅要區分客戶端A、B、C,然后把響應報文要轉給不同的主機。
- 還有同一臺主機不同進程發給其他服務器,服務器處理之后把響應報文都發到NAT路由器,NAT路由器要把響應給同一臺主機的不同進程,所以還需要端口號來區分同一臺主機不同進程!
所以數據包在經過NAT路由器進行轉發出去的時候,不僅要考慮源IP地址替換的問題,源端口號也要被替換。
因此路由器還要在自身內部維護一張NAT轉換表! 其實這張轉換表是KV式的轉換表。
(于是 又回到了我們的算法問題上面
- 公網IP肯定是不一樣的,局域網內不同主機IP地址也是不一樣的。
- 可能是同一臺主機不同進程發送請求,也有可能是不同主機但是端口號相同的進行發送請求
但是因為有IP和端口號的存在這個組成四元組的請求在局域網內一定是唯一四元組。
(??理解:雖然客戶端發的可能是去 同一個主機+port,但因為是不同客戶端發的,我們之后可能還涉及到了一個應答的問題,所以在 nat 轉發的時候,替換為相同的 轉發的源 ip 地址時,port 端口要設置的不同
- 把請求報文經過NAT路由器源IP地址的轉換,源port也可能轉換(可能一臺主機不同進程都訪問外網)。
- 替換之后這個四元組請求在公網內也是唯一的。
所以NAT路由表做了源IP替換和可能源port的替換之后變成了新的源IP和或者新的源port,因為一個是私網IP一個是公網IP,這兩種IP一定是不一樣的,也就是說替換完了之后兩個四元組的源IP一定是不一樣的 - 就注定了這兩個唯一的四元組合起來整體一定是不一樣的并且是唯一的。
所以在NAT路由器中就構建了互為鍵值的映射表 并且都是唯一的。
也就是說從左到右,左側的值是具有唯一性的,從右到左,右側的值也是具有唯一性的。
?
所以數據包回來到NAT路由器后,雖然NAT路由器看到目的IP都是一樣的,但是曾經端口號可能做了替換,因此看到的整體一定是不一樣的。
- 然后再根據映射,所以最終就可以區分出那個數據包是那個主機的進程的。
- 所以不用擔心數據包回來的問題,因為路上的路由器都會建立NAT轉換表。這個NAT轉換表可以從左到右查,也可以從右到左查。(有不同的端口,可以實現來區分返回)
- 經過剛才的分析,如果主機上從來沒有訪問外網,那外網能不能之間通過路由器訪問這臺主機呢?
不能!因為路上的路由器并沒有建立對應的映射關系。 - 所以也不能把數據反向的從外網推送給該主機。除非該主機訪問外網然后路上路由器記錄對應的映射關系,外網才能訪問該主機。
這種互為映射的關聯關系也是由NAT路由器自動維護的.
- 例如在TCP的情況下, 建立連接時, 就會生成這個表項;
- 在斷開連接后, 就會刪除這個表項
所以有NAT這種技術的存在可以讓我們構建各自各樣的子網,然后通過轉發訪問公網 - 只要保證最終的出口路由器(連接公網的路由器)它所面對公網的IP地址是唯一的就可以了,內網環境可以使用NAT進行各自轉發。
NAT技術的缺陷
由于NAT依賴這個轉換表, 所以有諸多限制:
- 無法從NAT外部向內部服務器建立連接; (NAT 只能從內向外)
- 轉換表的生成和銷毀都需要額外開銷;
- 通信過程中一旦NAT設備異常, 即使存在熱備, 所有的TCP連接也都會斷開;(這是一個很危險的事情)
2.NAT和代理服務器
代理服務器又分為正向代理和反向代理.
舉個例子
花王尿不濕是一個很經典的尿不濕品牌, 產自日本.
我自己去日本買尿不濕比較不方便, 但是可以讓我在日本工作的表姐去超市買了快遞給我. 此時超市看到的買家是我表姐, 我的表姐就是 “反向代理”;(代表的是日本超市)
就好比我自己在訪問時看不到真正服務器,我只知道要什么東西就找這個代理服務器要。這就是反向代理。
后來找我表姐買尿不濕的人太多了, 我表姐覺得天天去超市太麻煩, 干脆去超市買了一大批尿不濕屯在國內家里, 如果有人來找她代購, 就直接把屯在國內家里的貨發出去, 而不必再去超市. 此時我表姐就是 "正向代理(代表的是客戶)
代理客戶端的是 正向代理
代理服務端的是 反向代理
(相當于 這個正 是以客戶為正)
一般我們在公司層面,現在有很多機器,這些機器通過網絡訪問公司內部的服務器。
- 公司內部一定是有多種服務器的,可是有這么多服務器都暴露在公網里面, 每一臺服務器也都得有自己公網IP的話,那么最終我們應該訪問那一臺服務器呢?其實是比較尷尬的。
- 所以提供服務的一方呢,可能會給我們提供一個入口服務器。這個入口服務器一般會部署某些網絡服務,現在就變成了只有這一臺入口服務器有自己的公網IP,暴露在公網里,所有人都訪問這臺入口路由器,那么這一臺機器最終就可以把所有人請求轉發到它內網中某臺服務器來對外提供服務。
我們就把這臺入口機器稱之為代理服務器嚴格來說稱之為反向代理服務器。
- 假設現在有4千萬個客戶端,有大量的請求都過來的,反向代理服務器收到大量的請求,如果它內部沒有做任何處理,把請求全都給一臺機器或者少量幾臺機器
- 最終會導致這幾臺機器壓力非常大,其他機器很閑,最終導致這個集群承受壓力能力變得非常低。
- 所以反向代理為了解決這個問題,在自己內部中當有請求到來它內部有策略的把請求均衡的分散到整個集群所有主機上,這種策略我們稱之為負載均衡
-
一般作為反向代理服務器配置都比較高,上面通常充當反向代理服務的一般有一些軟件服務,比如說Nginx,它是一款web服務器也可以充當代理服務器。 - 一般把結果返回給客戶端有兩種做法,一種是把對應的處理結果返回給代理,然后由代理通過網絡轉發給客戶端。
另一種把對應的處理結果由內網機器直接通過網絡訪問客戶端。
反向代理通常作為機房入口機器,來實現分發和負載均衡
反向代理服務器在學校網絡環境中的應用
- 分發和負載均衡:反向代理通常作為機房或網絡入口,用于實現流量的分發和負載均衡,優化資源使用并提高訪問效率。
- 訪問控制與緩存:當學生嘗試通過校園網訪問外網時,實際上請求首先發送到學校的反向代理服務器。學校可以通過這道“門”限制學生的訪問,例如阻止訪問某些不被允許的內容。此外,如果多個用戶請求相同的內容(如觀看同一部電影),服務器可以緩存這些內容,并在后續請求中直接提供緩存版本,減少重復的公網訪問,提高響應速度。
- 禁止訪問:對于不允許訪問的公網資源,學校可以直接在反向代理服務器上丟棄這些請求,從而阻止學生訪問特定的外網內容。
- 上網認證與收費:為了管理校園網資源,學校可能會要求學生通過登錄頁面進行身份驗證和繳費確認后才能上網。所有網絡請求都需經過這臺服務器,確保只有通過認證且付費的用戶才能獲得公網訪問權限。這種方式不僅幫助學校管理網絡資源,還能保證用戶的身份安全和服務質量。
- 像這種服務器替用戶去訪問我們稱之為正向代理。它可以緩存資源,然后從學校層面可以根據正向代理服務器對學生所訪問的各種資源進行限定,同理也可以對學生做入網許可的管理,這就是正向代理。正向代理服務器歸根結底其實就是把所有請求收集在一起方便對請求本身做管理。
上面內容好像和剛才說的NAT有些類似,NAT是在做由路由器替用戶去進行請求,這不就是正向代理,那么是不是這樣呢?
- 路由器往往都具備NAT設備的功能, 通過NAT設備進行中轉, 完成子網設備和其他子網設備的通信過程.
- 代理服務器看起來和NAT設備有一點像. 客戶端像代理服務器發送請求, 代理服務器將請求轉發給真正要請求的服務器; 服務器返回結果后, 代理服務器又把結果回傳給客戶端.
- 看起來它們倆工作原理好像有一點像,但其他它們倆是不同的東西。
那么NAT和代理服務器的區別有哪些呢?
(即 如何理解 NAT 的轉發,和代理服務器的轉發 之間的區別
從應用上講,
- NAT設備是網絡基礎設備之一, 解決的是IP不足的問題.
- 代理服務器則是更貼近具體應用, 比如通過代理服務器進行翻墻, 另外像迅游這樣的加速器, 也是使用代理服務器.
從底層實現上講,
- NAT是工作在網絡層, 直接對IP地址進行替換.
- 代理服務器往往工作在應用層.
從使用范圍上講,
- NAT一般在局域網的出口部署,
- 代理服務器可以在局域網做, 也可以在廣域網做, 也可以跨網.
從部署位置上看,
- NAT一般集成在防火墻, 路由器等硬件設備上,
- 代理服務器則是一個軟件程序, 需要部署在服務器上.代理服務器是一種應用比較廣的技術.
- 翻墻: 廣域網中的代理.
- 負載均衡: 局域網中的代理.
3.網線通信各層協議總結
以下是對網絡通信各層協議的整理表格,按層級結構分類展示關鍵知識點:
協議層級 | 核心協議/概念 | 主要特點 | 關鍵知識點 |
數據鏈路層 | 以太網(Ethernet) | 實現同一局域網內設備間的直接通信 | ? 包含物理層規范(拓撲結構、傳輸速率) |
ARP協議 | 動態維護IP地址與MAC地址映射關系 | ? 通過廣播請求獲取目標MAC | |
網絡層 | IP協議(IPv4/IPv6) | 實現跨網絡的數據路由與尋址 | ? IP地址邏輯標識設備 |
ICMP協議 | 網絡狀態診斷與控制 | ? Ping命令基于ICMP回顯請求 | |
路由協議(如OSPF、BGP) | 動態選擇最優傳輸路徑 | ? 路由表維護目標網絡與下一跳關系 | |
傳輸層 | TCP協議 | 提供可靠、面向連接的端到端傳輸 | ? 三次握手建立連接/四次揮手斷開 |
UDP協議 | 提供高效、無連接的簡單傳輸服務 | ? 無連接、不可靠但延遲低 | |
應用層 | HTTP/HTTPS | 萬維網數據通信基礎 | ? 請求-響應模型(GET/POST等) |
DNS協議 | 域名與IP地址的映射系統 | ? 分層解析(根域名→頂級→權威) | |
自定義應用協議 | 滿足特定業務需求 | ? 需明確定義報文格式(頭部+載荷) |
補充說明
- 層級關聯:
? 數據鏈路層MAC地址用于局域網通信 → 網絡層IP地址實現跨網尋址 → 傳輸層端口號定位具體應用 → 應用層協議處理具體業務數據 - 典型交互流程:
- 協議設計原則:
? 下層為上層提供服務(如IP層依賴數據鏈路層傳輸)
? 協議開銷與效率的平衡(TCP可靠性 vs UDP高效率)
? 安全性分層實現(鏈路層加密/WiFi WPA2、傳輸層TLS、應用層HTTPS)
4.五種 IO 模型
1.什么是IO?什么是高效的IO?
在之前我們都知道的input,output不就是IO嗎
- 站在馮諾依曼體系角度我們知道從外設把數據搬到內存這不就是Input嗎
- 把數據從內存拷貝到外設中這不就是output嗎。
這不就是傳說中的IO嗎。沒錯,但是這種理解還不夠深刻!
- 當我們在網絡中發送數據的時候是使用write發生,read讀取。
- 當我們在進行write寫入的時候曾經說過,我們在應用層調用write本質并不是把數據發送到網絡中,其實只是把數據從應用層拷貝到傳輸層的發送緩沖區,所有write本質就是拷貝。
- 當我們調用read讀取數據時,其實并不是從網絡中讀取,而是從傳輸層的接收緩沖區中把數據從內核中拷貝到應用層,所以read也是拷貝函數。可是你想拷貝就能給你拷貝嗎?
你想write,有沒有可能發送緩沖區因為流量控制的問題發送緩沖區已經被寫滿了數據,你想write但當前緩沖區沒有空間讓你write了。
- 那么此時write操作默認就是阻塞在哪里,直到緩沖區有空間了
- write我們寫代碼到現在見到很少。但是當我們read讀取數據的時候,我們被阻塞的情況是非常常見的。
以讀為例,說讀取就是拷貝這句話沒錯,但是當你想拷貝就能拷貝嗎?
萬一人家接收緩沖區就沒有數據呢?你的read只能阻塞住。所以要記住read、write本質就是拷貝,但是拷貝是有條件的。
所以不用考慮操作系統,就站在read接口使用角度,調用read/recv… 有兩種情況
- 沒有數據,就會阻塞住
- 有數據,read/recv… 會在拷貝完成之后進行返回
這個阻塞不就是在等待資源就緒嗎。
所以不能簡單認為read/recv… 只有拷貝。這是不全面的認識。
read/recv… 讀取的本質應用要分成兩種東西。
- 站在我們角度read/recv…就是input。
- 讀取也是同樣如此要風兩種東西,write/send…就是output。
IO本質:
IO = 等 + 數據拷貝
在系統層面和網絡層面IO都叫數據拷貝,就比如寫文件的時候,把數據寫到文件的過程我們根本不知道,調用write也只是把文件寫到操作系統里,然后由操作系統把數據刷新到文件里。
- 同理,我們也沒有資格把數據直接寫到網絡里,只是把數據交給了操作系統,由操作系統幫我們發送。
- 所以我們發現系統和網絡在IO的處理上是一至的。
- 在系統的時候我們不說IO=等+數據拷貝,是因為在系統層面等這個事情不直觀,訪問一個本地文件很快就寫完成,很快就讀完成了。
- 看不到等。其實有沒有等呢?一定要等!今天就知道了,你要讀取數據,但數據可能并不在內存中,你必須要等,因為操作系統首先要把數據從外設(磁盤)搬到內存里。
- 而磁盤是外設,所以操作系統要給磁盤下達指令把數據從磁盤中拷貝到內存等工作做完了,然后你才把數據從操作系統拷貝到用戶,只不過這個過程太快了,你感受不到。
今天就不一樣,在網絡通信距離變長了,還要流量控制、擁塞控制等,所以距離一長等的比重就顯得明顯了,就能感覺到IO=等+數據拷貝了。
什么是高效的IO?
你經常會聽別人說我們要高效的IO,憑什么?你IO高效的提高究竟是在做哪方面的提高?
- 首先數據拷貝這件事情,它的效率是固定的。
- 因為數據拷貝的的本質是從硬件到硬件,該花多少時間就花多少時間,要么就是由你主機上的總線的位寬決定的,要么就是由你網絡的帶寬決定的。
- 所以這個東西本身就是確定的,只要你能保證你在拷貝的時候它在100%一直在拷貝,它的效率就已經到達上限了。
既然IO = 等 + 數據拷貝,那什么叫做高效IO呢?
其實,只要減少 等待 的比重,即可!
- 想象一下調用read只花1秒,可是其中有99%的時間都在等待,等待的事件永遠是主要矛盾,那么只有1%的時間花在拷貝上,拷貝本身就是從操作系統拷貝到用戶,它是從內核到用戶。
- 站在硬件角度上就是從內存到內存,這個時間本身就是一個固定時間,站在操作系統角度把數據從外設搬到內存,把硬件上速度拉滿它能拷貝多少就是多少。
- 可是在IO大部分時間在等,如果把等和數據拷貝時間反過了,99%在拷貝,1%在等
- 我調用read很快就能夠或者等的比重降的非常低,一調用read就直接返回,那這就叫做高效IO。
而在 等待 這件事情上,我們是需要從軟件策略完成的。
read/recv它們策略很簡單粗暴,沒有數據就等,有數據就拷貝。
2.有那些IO的方式?這么多的方式,有那些是高效的?
下面講個小故事理解IO的過程。
我們可能見過別人釣魚或者自己釣魚,那么把釣魚步驟化繁為簡,釣魚分兩步
釣魚 = 等 + 釣
釣魚:
- 張三:專注死盯魚漂,不達目的不罷休 → 阻塞IO
- 李四:三心二意,頻繁查看魚漂 → 非阻塞IO
- 王五:掛鈴鐺通知,輕松做其他事 → 信號驅動IO
- 趙六:百竿齊發,來回巡檢 → 多路復用(select/epoll)
- 田七:完全委托小王,自己專注工作 → 異步IO
效率分析:
- 直觀效率:趙六 > 王五 > 張三 > 李四 > 田七(但田七實現了零等待)
- 理論極限:田七的異步模式效率最高(系統代為完成所有操作)
- 現實折中:趙六的多路復用是性價比最高的方案
關鍵對應關系:
| 故事元素 | 計算機概念 | 性能瓶頸 |
|---------------|-----------------------|----------------------|
| 魚漂動靜 | 數據就緒事件 | 事件檢測延遲 |
| 100根魚竿 | 多文件描述符 | 系統監控上限 |
| 來回巡檢 | select輪詢 | O(n)時間復雜度 |
| 鈴鐺響動 | SIGIO信號 | 信號處理延遲 |
| 小王代勞 | aio_read異步調用 | 系統回調機制 |
現實工程啟示:
- 阻塞IO:簡單但浪費線程資源(如傳統socket編程)
- 非阻塞IO:CPU空轉嚴重(需配合超時機制)
- 信號驅動:適用于低頻事件(如串口通信)
- 多路復用:高并發基石(nginx/epoll模型)(可以一次等待多個)
- 異步IO:未來方向但實現復雜(如Windows IOCP)
💡 在Linux中,
epoll_wait
就是趙六的"巡檢優化版",用紅黑樹管理魚竿(文件描述符),事件通知復雜度降為O(1)
所以,釣魚的人,等的比重比較低,單位時間,釣魚的效率就高!
- 其次,張三,李四,王五,趙六,田七(小王)誰釣魚效率最高?
- 首先張三、李四、王五、田七(小王)它們只有一人一竿,只有趙六是一人多竿。魚竿多就是了不起。假設趙六100條竿,加上其他的人4條竿。
- 站在魚的角度頭頂上有著104個誘餌,咬到任何一個誘餌概率是一樣的,要是咬的話,趙六釣魚成功概率就是100/104,其他人只是1/104,所以趙六釣魚時任一魚竿就緒概率概率就100/104。
- 所以單位時間內任何一個魚竿就緒概率就是比其他人大。所以站在旁觀者看趙六就可能一直有魚咬竿的事情。
- 所以單位時間內,趙六這種釣魚方式等的比重比較低,所以趙六釣魚的效率比較高。
我們把這種一次可以等待多個魚竿的釣魚方式叫做多路轉接/多路復用
張三 ------> 阻塞IO
李四 ------> 非阻塞IO
王五 ------> 信號驅動式IO(還沒有釣魚就知道鈴鐺響了,魚就咬鉤了)
趙六 ------> 多路轉接/多路復用
田七 (小王) ------> 異步IO
- 張三、李四,王五、趙六、田七 ----> 進程/線程
小王 ----> OS
魚 ----> 數據
河 ----> 內核空間
魚鰾 ----> 數據就緒的事件
魚竿 ----> 文件描述符
釣魚的動作 ----> read/recv…釣魚
理解:
- 當張三這個進程去讀數據時,只要底層數據沒有就緒,就要一直等待將自己掛起。只有數據就緒了,才會被喚醒然后讀到數據在返回。
- 李四這個進程去讀數據時,當底層數據沒有就緒,李四并不會因為read/recv…而被阻塞,而是立馬返回,在自己的while循環中去做其他事情。然后再去讀(可以設定詢問間隔時間)。
- 王五這個進程在進行IO之前,一旦IO了操作系統會給進程推送SIGIO信號(需要特定接口去設置),王五在進行調用recv之前,他只是注冊一下SIGIO的方法,然后王五繼續向后執行做自己的事情,一旦有IO就緒了,王五的信號捕捉方法里直接調用recv,然后把數據從內核拷貝到用戶空間,這叫做信號驅動。
- 趙六這個進程拿著多個文件描述符,一次等待多個,具體怎么等后面說。
- 田七這個進程,通過異步IO的接口直接將數據讀取的工作交給操作系統,除了把任務交給操作系統同時他還給了操作系統一個緩沖區(魚桶),以及給了操作系統一個通知(電話),比如是某些回調方法或者某些回調策略。
- 讓操作系統在讀取數據時直接把數據全部從內核中讀取到緩沖區,然后用告訴操作系統的方法,來告知田七數據準備好了讓田七直接用就好了,這就叫做異步IO。
所以我們把上面對IO的方式,我們稱之為五種IO模型。所有IO都隸屬于上面模型,目前大部分使用的文件接口用的是阻塞IO。
而多路轉接/多路復用是比較高效的
對比五種IO模型的差別
張三、李四、王五在效率上有差別嗎?
沒有!因為他們在整個IO過程,該等多少時間就等了多少時間。效率上是沒有差別的。都只有一個魚竿,而魚釣上來的概率是一樣的。
但是在其他方面有差別!阻塞式什么事都不干,只進行IO,所以其他方面沒有優勢。
- 而非阻塞式IO,它可以輪詢式的方法檢測底層數據是否就緒,在檢測沒有數據就緒時還可以在等的時間做其他事情。
- 信號驅動也是一樣的,在等待數據就緒時,也同樣在等的時間做其他事情。
- 所以張三、李四、王五在IO上效率是一樣的,但是整體上李四,王五可以做其他事情,表現上他們好像多做了事情然后更高效一點,但是這高效沒有體現在IO上。
王五(信號驅動)究竟有沒有等待呢?
他一定等了,要不然王五早就走了,為什么還要待在岸邊呢?所以本質上還是等了。
只不過等的方式有些差別,別人是主動去檢測,而他變成了你好了,你來叫我。信號驅動是采用回調的方式來進行等待的。
- 張三、李四、王五、趙六他們其實每一個人都等了,當魚咬鉤時每一個人都釣了。每一個人都參與了IO的過程,我們把他們都可以稱之為同步IO。
- 田七并沒有等魚咬鉤,也沒有當魚咬鉤時把魚釣上來,他連河邊都沒有去過,他把任務交給小王,并沒有參與IO的兩個階段中的任何一個階段,我們把他稱之為異步IO。
- (他都沒有進行 IO,直接拿到了結果,我們之前寫的 rpc 就是一個異步 IO,但是謹記,沒有銀彈,異步有時容易造成混亂,所以我們有時會采用協程)
阻塞式IO和非阻塞式IO有什么差別呢?
共同點:釣
不同點:等的方式不同!
異步這里好理解,但是同步這里就有一個問題了,我們曾經學過一個線程同步的概念。
- 現在又學了一個同步IO,那這兩個同步是一樣的嗎?
它們之間的關系就和老婆和老婆餅一樣,沒有任何關系!當我們在網絡中搜索同步的概念時一定要加前提條件。線程同步是讓多線程執行具有一定的順序性。還是說IO的同步允許參與IO的過程!
為什么多路轉接/多路復用是高效的代名詞?
因為 IO = 等 + 數據拷貝,多路轉接/多路復用可以減少等的比重
同樣等,但是一次可以等待多個文件描述符至少有一個就緒。調用read等的比重降低了,未來效率就高了。
異步 IO
異步I/O的一個缺點是在某些情況下可能會導致更多的上下文切換和更高的開銷,因為操作系統需要頻繁地在多個任務之間進行調度。
用之前釣魚故事中的「田七委托小王」場景來解釋異步IO的缺點:
🎣 關鍵缺陷類比
田七(應用程序) 小王(操作系統)│ ││ 委托釣魚任務 ││─────────────────────────>││ ├─ 需要準備魚竿、魚餌、水桶...│ ├─ 必須完全信任小王的釣魚能力│ ├─ 無法實時查看釣魚進度│ │(突然下暴雨也不知道)│ ││ ←─── 桶滿才通知 ──── │
📝 具體技術缺陷
- 開發復雜度劇增
-
- 需要設計復雜的回調機制(如同田七要給小王寫詳細釣魚指南)
- 狀態管理困難(無法直觀看到魚竿的實時狀態)
- 示例代碼復雜度對比:
// 同步模式(張三式)
fish = wait_and_catch(); // 簡單直觀// 異步模式(田七式)
start_async_catch(callback_func);
while(1){ /* 處理其他事但需維護回調狀態 */ }
- 系統支持碎片化
操作系統 | 實現方式 | 如同... |
Linux | libaio/epoll | 小王只會傳統釣魚法 |
Windows | IOCP | 小王會用智能釣魚機器人 |
macOS | kqueue | 小王擅長海釣 |
- 調試噩夢
-
- 回調鏈斷裂時難以追溯(如小王釣到魚但忘記打電話)
- 多線程+異步的競態條件(多個小王同時操作魚桶)
- 資源隱性消耗
-
- 每個異步操作需要維護上下文(如同每個魚竿要配記錄本)
- 事件循環本身消耗CPU(田七頻繁看手機是否收到通知)
- 所以他哪怕去干別的事了,只等結果,但是他等的也不安心..(異步IO? 存在的問題)
- 不適用場景
┌───────────────┬───────────────┐
│ 適合場景 │ 如同... │
├───────────────┼───────────────┤
│ 大規模并發請求 │ 趙六式百竿監控 │(多路復用)
├───────────────┼───────────────┤
│ 簡單串行任務 │ 張三式專注單竿 │
└───────────────┴───────────────┘
?? 實踐中的坑
- 緩沖區管理失控:如同小王釣的魚太多,桶溢出導致魚逃跑(內存泄漏)
- 超時機制缺失:若小王永遠釣不滿桶,田七會永久等待(死鎖)
- 優先級反轉:緊急釣魚需求可能被普通請求阻塞
建議在需要處理 10,000+ 并發連接 的場景(如高頻交易系統)才考慮純異步方案,其他情況可結合多路復用(趙六模式)+線程池優化。
下篇文章我們將繼續詳細講解代碼實現~