網絡包接收流程
01.當網絡數據幀
通過網絡傳輸到達網卡時蛤袒,網卡會將網絡數據幀
通過DMA
拷貝的方式放到DMA環(huán)形緩沖區(qū)RingBuffer
中统阿;
環(huán)形緩沖區(qū)
RingBuffer
是網卡在啟動的時候分配和初始化的環(huán)形緩沖隊列钝凶;當RingBuffer
滿的時候叹坦,新來的數據包就會被丟棄跪解;我們可以通過ifconfig
命令查看網卡收發(fā)數據包的情況,其中overruns
數據項表示當RingBuffer
滿時被丟棄的數據包跌榔,如果發(fā)現出現丟包情況,可以通過ethtool
命令來增大RingBuffer
長度 捶障;
02.當DMA
操作完成時僧须,網卡會向CPU
發(fā)起一個硬中斷
,告訴CPU
有網絡數據到達项炼;CPU
調用網卡驅動注冊的硬中斷響應程序
担平; 網卡硬中斷響應程序
會為網絡數據幀創(chuàng)建內核數據結構sk_buffer
,并將網絡數據幀
拷貝到sk_buffer
中锭部;然后發(fā)起軟中斷
請求暂论,通知內核有新的網絡數據幀到達 ;
緩沖區(qū)
sk_buff
拌禾,是一個維護網絡幀結構的雙向鏈表取胎,鏈表中的每一個元素都是一個網絡幀
;雖然TCP/IP
協議棧分了好幾層,但上下不同層之間的傳遞闻蛀,實際上只需要操作這個數據結構中的指針匪傍,而無需進行數據復制;
03.內核線程ksoftirqd
發(fā)現軟中斷
請求觉痛,調用網卡驅動注冊的poll
函數役衡;poll
函數將sk_buffer
中的網絡數據包
送到內核協議棧
中注冊的ip_rcv
函數中;
每個
CPU
會綁定一個ksoftirqd
內核線程專門用來處理軟中斷
響應秧饮;2個CPU
時映挂,就會有ksoftirqd/0
和ksoftirqd/1
這兩個內核線程 ;這里有個事情需要注意下: 網卡接收到數據后盗尸,當
DMA拷貝完成
時柑船,向CPU發(fā)出硬中斷
,這時哪個CPU
上響應了這個硬中斷
泼各,那么在網卡硬中斷響應程序
中發(fā)出的軟中斷
請求也會在這個CPU
綁定的ksoftirqd
線程中響應鞍时;所以如果發(fā)現Linux
軟中斷,CPU
消耗都集中在一個核上的話扣蜻,那么就需要調整硬中斷的CPU
親和性逆巍,來將硬中斷打散到不同的CPU
核上去
04.在ip_rcv
函數即網絡層中,取出數據包的IP頭
莽使,判斷該數據包下一跳的走向锐极,如果數據包是發(fā)送給本機的,則取出傳輸層的協議類型(TCP
或者UDP
)芳肌,并去掉數據包的IP頭
灵再,將數據包交給傳輸層處理;
傳輸層的處理函數:
TCP協議
對應內核協議棧中注冊的tcp_rcv函數
亿笤,UDP協議
對應內核協議棧中注冊的udp_rcv
函數翎迁;
05.當我們采用的是TCP
協議時,數據包到達傳輸層時净薛,會在內核協議棧中的tcp_rcv
函數處理汪榔,在tcp_rcv
函數中去掉TCP
頭,根據四元組(源IP,源端口,目的IP,目的端口)
查找對應的Socket
肃拜,如果找到對應的Socket
則將網絡數據包
中的傳輸數據拷貝到Socket
中的接收緩沖區(qū)
中痴腌;如果沒有找到,則發(fā)送一個目標不可達的icmp
包燃领;
06.當我們程序通過系統(tǒng)調用read
讀取Socket接收緩沖區(qū)
中的數據時衷掷,如果接收緩沖區(qū)中沒有數據,那么應用程序就會在系統(tǒng)調用上阻塞柿菩,直到Socket
接收緩沖區(qū)有數據,然后CPU
將內核空間
(Socket
接收緩沖區(qū))的數據拷貝
到用戶空間雨涛,最后系統(tǒng)調用read
返回枢舶,應用程序讀取數據 懦胞;
網絡包發(fā)送流程
01.當我們在應用程序中調用send
系統(tǒng)調用發(fā)送數據時,由于是系統(tǒng)調用所以線程會發(fā)生一次用戶態(tài)到內核態(tài)的轉換凉泄,在內核中首先根據fd
將真正的Socket
找出躏尉,這個Socket
對象中記錄著各種協議棧的函數地址,然后構造struct msghdr
對象后众,將用戶需要發(fā)送的數據全部封裝在這個struct msghdr
結構體中胀糜;
02.調用內核協議棧
函數inet_sendmsg
,發(fā)送流程進入內核協議棧
處理蒂誉;在進入到內核協議棧
之后教藻,內核會找到Socket
上的具體協議的發(fā)送函數;比如:我們使用的是TCP協議
右锨,對應的TCP協議
發(fā)送函數是tcp_sendmsg
括堤,如果是UDP協議
的話,對應的發(fā)送函數為udp_sendmsg
绍移;
03.在TCP協議
的發(fā)送函數tcp_sendmsg
中悄窃,創(chuàng)建內核數據結構sk_buffer
,將struct msghdr
結構體中的發(fā)送數據拷貝到sk_buffer
中蹂窖;調用tcp_write_queue_tail
函數獲取Socket
發(fā)送隊列中的隊尾元素轧抗,將新創(chuàng)建的sk_buffer
添加到Socket
發(fā)送隊列(雙向鏈表
)的尾部;
發(fā)送流程走到這里瞬测,用戶要發(fā)送的數據總算是從
用戶空間
拷貝到了內核
中横媚,這時雖然發(fā)送數據已經拷貝
到了內核Socket
中的發(fā)送隊列中,但并不代表內核會開始發(fā)送涣楷,因為TCP協議
的流量控制
和擁塞控制
分唾,用戶要發(fā)送的數據包并不一定會立馬被發(fā)送出去,需要符合TCP協議
的發(fā)送條件狮斗;如果沒有達到發(fā)送條件绽乔,那么本次send
系統(tǒng)調用就會直接返回 ; 如果符合發(fā)送條件碳褒,則開始調用tcp_write_xmit
內核函數折砸;在這個函數中,會循環(huán)獲取Socket
發(fā)送隊列中待發(fā)送的sk_buffer
沙峻,然后進行擁塞控制
以及滑動窗口
的管理睦授, 將從Socket
發(fā)送隊列中獲取到的sk_buffer
重新拷貝一份,設置sk_buffer
副本中的TCP HEADER
摔寨;其實在sk_buffer
內部其實包含了網絡協議中所有的header
去枷,在設置TCP HEADER
的時候,只是把指針指向sk_buffer
的合適位置,后面再設置IP HEADER
的時候删顶,在把指針移動一下就行竖螃,避免頻繁的內存申請和拷貝,效率很高逗余;
04.當設置完TCP頭
后特咆,內核協議棧
的傳輸層
的事情就做完了,下面通過調用ip_queue_xmit
內核函數录粱,正式來到內核協議棧網絡層
的處理腻格;
- 將
sk_buffer
中的指針移動到IP頭
位置上,設置IP
頭 啥繁;
- 執(zhí)行
netfilters
過濾鸥滨,過濾通過之后终吼,如果數據大于MTU
的話杠氢,則執(zhí)行分片 昂勉;
- 檢查
Socket
中是否有緩存路由表,如果沒有的話宪睹,則查找路由項愁茁,并緩存到Socket
中;接著在把路由表設置到sk_buffer
中 亭病;
05.內核協議棧網絡層
的事情處理完后鹅很,現在發(fā)送流程進入了到了鄰居子系統(tǒng)
,鄰居子系統(tǒng)
位于內核協議棧中的網絡層
和網絡接口層
之間罪帖,用于發(fā)送ARP請求
獲取MAC地址
促煮,然后將sk_buffer
中的指針移動到MAC頭
位置,填充MAC頭
整袁;
06.經過鄰居子系統(tǒng)
的處理菠齿,現在sk_buffer
中已經封裝了一個完整的數據幀
,隨后內核將sk_buffer
交給網絡設備子系統(tǒng)
進行處理坐昙;
- 選擇發(fā)送隊列(
RingBuffer
)绳匀,因為網卡擁有多個發(fā)送隊列,所以在發(fā)送前需要選擇一個發(fā)送隊列
- 將
sk_buffer
添加到發(fā)送隊列中炸客;
- 循環(huán)從發(fā)送隊列(
RingBuffer
)中取出sk_buffer
疾棵,調用內核函數sch_direct_xmit
發(fā)送數據,其中會調用網卡驅動程序來發(fā)送數據 痹仙;
07.現在發(fā)送流程到了網卡真實發(fā)送數據的階段是尔,無論是用戶線程的內核態(tài)還是觸發(fā)NET_TX_SOFTIRQ
類型的軟中斷在發(fā)送數據的時候最終會調用到網卡的驅動程序函數dev_hard_start_xmit
來發(fā)送數據;在網卡驅動程序函數dev_hard_start_xmit
中會將sk_buffer
映射到網卡可訪問的內存 DMA
區(qū)域开仰,最終網卡驅動程序通過DMA
的方式將數據幀
通過物理網卡發(fā)送出去拟枚;
08.當數據發(fā)送完畢后薪铜,還有最后一項重要的工作,就是清理工作梨州;數據發(fā)送完畢后痕囱,網卡設備會向CPU
發(fā)送一個硬中斷,CPU
調用網卡驅動程序注冊的硬中斷響應程序
暴匠,在硬中斷響應中觸發(fā)NET_RX_SOFTIRQ
類型的軟中斷,在軟中斷的回調函數igb_poll
中清理釋放 sk_buffer
傻粘,清理網卡發(fā)送隊列(RingBuffer
)每窖,解除 DMA
映射;
無論硬 中斷是因為有數據要接收弦悉,還是說發(fā)送完成通知窒典,從硬中斷觸發(fā)的軟中斷都是
NET_RX_SOFTIRQ
; 這里釋放清理的只是sk_buffer
的副本稽莉,真正的sk_buffer
現在還是存放在Socket
的發(fā)送隊列中瀑志;它得等收到對方ACK
之后才會真正刪除;
阻塞非阻塞
將接收網絡包流程分為數據準備階段
和數據拷貝階段
污秆;阻塞與非阻塞的區(qū)別主要發(fā)生在數據準備階段
劈猪,同步與異步的區(qū)別主要發(fā)生在數據拷貝階段
;
數據準備階段:在這個階段良拼,網絡數據包到達網卡战得,通過
DMA
的方式將數據包拷貝到內存中,然后經過硬中斷庸推,軟中斷常侦,接著通過內核線程ksoftirqd
經過內核協議棧的處理,最終將數據發(fā)送到內核Socket
的接收緩沖區(qū)中贬媒;數據拷貝階段:當數據到達內核
Socket
的接收緩沖區(qū)中時聋亡,此時數據存在于內核空間中,需要將數據拷貝到用戶空間中际乘,才能夠被應用程序讀绕戮蟆;
阻塞: 當應用程序發(fā)起系統(tǒng)調用read
時蚓庭,線程從用戶態(tài)
轉為內核態(tài)
致讥,讀取內核Socket
的接收緩沖區(qū)中的網絡數據;如果這時內核Socket
的接收緩沖區(qū)沒有數據器赞,那么線程就會一直等待垢袱,直到Socket
接收緩沖區(qū)有數據為止;隨后將數據從內核空間拷貝到用戶空間港柜,系統(tǒng)調用read
返回 请契;
非阻塞:當應用程序發(fā)起系統(tǒng)調用read
時咳榜,線程從用戶態(tài)
轉為內核態(tài)
,讀取內核Socket
的接收緩沖區(qū)中的網絡數據爽锥;如果這時內核Socket
的接收緩沖區(qū)沒有數據涌韩,應用程序不會等待,系統(tǒng)調用直接返回錯誤標志EWOULDBLOCK
氯夷,直到Socket
接收緩沖區(qū)有數據為止臣樱;隨后將數據從內核空間拷貝到用戶空間,系統(tǒng)調用read
返回 腮考;
同步與異步
同步:在數據準備好后雇毫,是由用戶線程的內核態(tài)
來執(zhí)行第二階段;所以應用程序會在第二階段發(fā)生阻塞踩蔚,直到數據從內核空間
拷貝到用戶空間
棚放,系統(tǒng)調用才會返回;Linux
下的 epoll
和Mac
下的 kqueue
都屬于同步 IO
馅闽;
異步:在數據準備好后飘蚯,是由內核
來執(zhí)行第二階段的數據拷貝操作;當內核
執(zhí)行完第二階段福也,會通知用戶線程IO
操作已經完成局骤,并將數據回調給用戶線程;所以在異步模式下 數據準備階段
和數據拷貝階段
均是由內核
來完成拟杉,不會對應用程序造成任何阻塞庄涡;
網絡IO模型
在進行網絡IO
操作時,用什么樣的IO
模型來讀寫數據將在很大程度上決定了網絡框架的IO
性能搬设,所以IO
模型的選擇是構建一個高性能網絡框架的基礎穴店,在《UNIX 網絡編程
》一書中介紹了五種IO
模型:阻塞IO
,非阻塞IO
,IO多路復用
,信號驅動IO
,異步IO
,每一種IO
模型的出現都是對前一種的升級優(yōu)化拿穴;
阻塞IO(BIO)
阻塞讀: 當用戶線程發(fā)起
read
系統(tǒng)調用泣洞,用戶線程從用戶態(tài)
切換到內核態(tài)
,在內核中去查看Socket
接收緩沖區(qū)是否有數據到來 默色;Socket
接收緩沖區(qū)中有數據 球凰,則用戶線程在內核態(tài)將內核空間中的數據拷貝到用戶空間,系統(tǒng)IO
調用返回腿宰;Socket
接收緩沖區(qū)中無數據呕诉,則用戶線程讓出CPU
,進入阻塞狀態(tài)吃度,當數據到達Socket
接收緩沖區(qū)后甩挫,內核喚醒阻塞狀態(tài)中的用戶線程進入就緒狀態(tài),隨后經過CPU
的調度獲取到CPU quota
進入運行狀態(tài)椿每,將內核空間的數據拷貝到用戶空間伊者,隨后系統(tǒng)調用返回英遭;阻塞寫: 當用戶線程發(fā)起
send
系統(tǒng)調用時,用戶線程從用戶態(tài)切換到內核態(tài)亦渗,將發(fā)送數據從用戶空間拷貝到內核空間中的Socket
發(fā)送緩沖區(qū)中挖诸; 當Socket
發(fā)送緩沖區(qū)能夠容納下發(fā)送數據時,用戶線程會將全部的發(fā)送數據寫入Socket
緩沖區(qū)法精,然后執(zhí)行后續(xù)流程多律,最后返回; 當Socket
發(fā)送緩沖區(qū)空間不夠亿虽,無法容納下全部發(fā)送數據時菱涤,用戶線程讓出CPU
,進入阻塞狀態(tài)洛勉,直到Socket
發(fā)送緩沖區(qū)能夠容納下全部發(fā)送數據時,內核喚醒用戶線程如迟,執(zhí)行后續(xù)發(fā)送流程收毫,最后返回;
阻塞IO
模型:由于阻塞IO
的讀寫特點殷勘,所以導致在阻塞IO
模型下此再,每個請求都需要被一個獨立的線程處理;一個線程在同一時刻只能與一個連接綁定玲销,來一個請求输拇,服務端就需要創(chuàng)建一個線程用來處理請求,當客戶端請求的并發(fā)量突然增大時贤斜,服務端在一瞬間就會創(chuàng)建出大量的線程策吠,而創(chuàng)建線程是需要系統(tǒng)資源開銷的,這樣一來就會一瞬間占用大量的系統(tǒng)資源瘩绒;如果客戶端創(chuàng)建好連接后猴抹,但是一直不發(fā)數據,那么在空閑的這段時間內锁荔,服務端線程就會一直處于阻塞狀態(tài)蟀给,無法干其他的事情,CPU
也無法得到充分的發(fā)揮阳堕,同時還會導致大量線程切換的開銷跋理;
非阻塞IO(NIO)
非阻塞讀: 當用戶線程發(fā)起非阻塞
read
系統(tǒng)調用時,用戶線程從用戶態(tài)轉為內核態(tài)恬总,在內核中去查看Socket
接收緩沖區(qū)是否有數據到來前普;Socket
接收緩沖區(qū)中無數據,系統(tǒng)調用立馬返回越驻,并帶有一個EWOULDBLOCK
或EAGAIN
錯誤汁政,這個階段用戶線程不會阻塞道偷,也不會讓出CPU
,而是會繼續(xù)輪訓直到Socket
接收緩沖區(qū)中有數據為止记劈;Socket
接收緩沖區(qū)中有數據勺鸦,用戶線程在內核態(tài)會將內核空間中的數據拷貝到用戶空間,注意:這個數據拷貝階段目木,應用程序是阻塞的换途,當數據拷貝完成,系統(tǒng)調用返回 刽射;非阻塞寫: 當用戶線程發(fā)起非阻塞
send
系統(tǒng)調用時军拟,當發(fā)送緩沖區(qū)中沒有足夠的空間容納全部發(fā)送數據時,非阻塞寫的特點是能寫多少寫多少誓禁,寫不下了懈息,就立即返回,并將寫入到發(fā)送緩沖區(qū)的字節(jié)數返回給應用程序摹恰,方便用戶線程不斷的輪訓嘗試將剩下的數據寫入發(fā)送緩沖區(qū)中 辫继;
非阻塞IO
模型:阻塞IO
模型最大的問題就是一個線程只能處理一個連接,如果這個連接上沒有數據的話俗慈,那么這個線程就只能阻塞在系統(tǒng)IO
調用上姑宽,不能干其他的事情,這對系統(tǒng)資源來說闺阱,是一種極大的浪費炮车,同時大量的線程上下文切換,也是一個巨大的系統(tǒng)開銷 酣溃, 基于這個需求瘦穆,第一種解決方案非阻塞IO
就出現了 ;基于以上非阻塞IO
的特點救拉,我們就不必像阻塞IO
那樣為每個請求分配一個線程去處理連接上的讀寫了难审,我們可以利用一個線程或者很少的線程,去不斷地輪詢每個Socket
的接收緩沖區(qū)是否有數據到達亿絮,如果沒有數據告喊,不必阻塞線程,而是接著去輪詢下一個Socket
接收緩沖區(qū)派昧,直到輪詢到數據后黔姜,處理連接上的讀寫或者交給業(yè)務線程池去處理),輪詢線程則繼續(xù)輪詢其他的Socket
接收緩沖區(qū)蒂萎;
IO多路復用
select:
select
是操作系統(tǒng)內核提供給我們使用的一個系統(tǒng)調用秆吵,它解決了在非阻塞IO
模型中需要不斷的發(fā)起系統(tǒng)IO
調用去輪詢各個連接上的Socket
接收緩沖區(qū)所帶來的用戶空間
與內核空間
不斷切換的系統(tǒng)開銷,轉而交給內核來幫我們完成:
- 首先用戶線程在發(fā)起
select
系統(tǒng)調用的時候會阻塞在select
系統(tǒng)調用上五慈,此時用戶線程從用戶態(tài)
切換到了內核態(tài)
完成了一次上下文切換纳寂;
- 用戶線程將需要監(jiān)聽的
Socket
對應的文件描述符fd
數組通過select
系統(tǒng)調用傳遞給內核主穗,此時用戶線程將用戶空間中的文件描述符fd
數組拷貝到內核空間;
- 當用戶線程調用完
select
后開始進入阻塞狀態(tài)毙芜,內核開始輪詢遍歷fd
數組忽媒,查看fd
對應的Socket
接收緩沖區(qū)中是否有數據到來,如果有數據到來腋粥,則將fd
對應BitMap
的值設置為1
晦雨,如果沒有數據到來,則保持值為0
隘冲;
- 內核遍歷一遍
fd
數組后闹瞧,如果發(fā)現有些fd
上有IO
數據到來,則將修改后的fd
數組返回給用戶線程展辞,此時會將fd
數組從內核空間
拷貝到用戶空間
奥邮;
- 當內核將修改后的
fd
數組返回給用戶線程后,用戶線程解除阻塞罗珍,由用戶線程開始遍歷fd
數組然后找出fd
數組中值為1
的Socket
文件描述符漠烧,最后對這些Socket
發(fā)起系統(tǒng)調用讀取數據 ;
- 由于內核在遍歷的過程中已經修改了
fd
數組靡砌,所以在用戶線程遍歷完fd
數組后獲取到IO
就緒的Socket
后,就需要重置
fd數組珊楼,并重新調用select
傳入重置后的fd
數組通殃,讓內核發(fā)起新的一輪遍歷輪詢 ;雖然
select
解決了非阻塞IO
模型中頻繁發(fā)起系統(tǒng)調用的問題厕宗,但是在整個select
工作過程中画舌,我們還是看出了select
有些不足的地方:(1)在發(fā)起select
系統(tǒng)調用以及返回時,用戶線程各發(fā)生了一次用戶態(tài)
到內核態(tài)
以及內核態(tài)
到用戶態(tài)
的上下文切換開銷已慢,發(fā)生2次上下文切換曲聂;(2)在發(fā)起select
系統(tǒng)調用以及返回時,用戶線程在內核態(tài)需要將文件描述符集合從用戶空間拷貝到內核空間佑惠,以及在內核修改完文件描述符集合后朋腋,又要將它從內核空間拷貝到用戶空間膜楷,發(fā)生2次文件描述符集合的拷貝;(3)雖然由原來在用戶空間發(fā)起輪詢優(yōu)化成了在內核空間發(fā)起輪詢但select
不會告訴用戶線程到底是哪些Socket
上發(fā)生了IO
就緒事件,只是對IO
就緒的Socket
作了標記,用戶線程依然要遍歷文件描述符集合去查找具體IO
就緒的Socket
,時間復雜度依然為O(n)
洽洁;(4)內核會對原始的文件描述符集合進行修改,導致每次在用戶空間重新發(fā)起select
調用時,都需要對文件描述符集合進行重置总放; (5)BitMap
結構的文件描述符集合炬搭,長度為固定的1024
衣形,所以只能監(jiān)聽0~1023
的文件描述符 ;(6)select
系統(tǒng)調用不是線程安全的句狼;
poll:
poll
相當于是改進版的select
笋熬,select
中使用的文件描述符集合是采用的固定長度為1024的BitMap
結構的fd_set
,而poll
換成了pollfd
結構沒有固定長度的數組腻菇,這樣就沒有了最大描述符數量的限制 胳螟;poll
只是改進了select
只能監(jiān)聽1024
個文件描述符的數量限制,但是并沒有在性能方面做出改進筹吐,和select
上本質并沒有多大差別 糖耸, 依然無法解決C10K
問題 ;
epoll:
IO多路復用模型:雖然非阻塞IO
模型與阻塞IO
模型相比丘薛,減少了很大一部分的資源消耗和系統(tǒng)開銷嘉竟,但是它仍然有很大的性能問題,因為在非阻塞IO
模型下洋侨,需要用戶線程去不斷地發(fā)起系統(tǒng)調用去輪訓Socket
接收緩沖區(qū)舍扰,這就需要用戶線程不斷地從用戶態(tài)切換到內核態(tài),內核態(tài)切換到用戶態(tài)希坚,隨著并發(fā)量的增大边苹,這個上下文切換的開銷也是巨大的,所以單純的非阻塞IO
模型還是無法適用于高并發(fā)的場景裁僧,只能適用于C10K
以下的場景勾给;這就需要操作系統(tǒng)的內核來支持這樣的操作,我們可以把頻繁的輪詢操作交給操作系統(tǒng)內核來替我們完成锅知,這樣就避免了在用戶空間頻繁的去使用系統(tǒng)調用來輪詢所帶來的性能開銷;
信號驅動IO
在信號驅動IO
模型下脓钾,用戶進程操作通過系統(tǒng)調用 sigaction
函數發(fā)起一個IO
請求售睹,在對應的socket
注冊一個信號回調,此時不阻塞用戶進程可训,進程會繼續(xù)工作昌妹,當內核數據就緒時,內核就為該進程生成一個 SIGIO
信號握截,通過信號回調通知進程進行相關IO
操作 飞崖; 信號驅動 IO
模型 相比于前三種 IO
模型,實現了在等待數據就緒時谨胞,進程不被阻塞固歪,主循環(huán)可以繼續(xù)工作,所以理論上性能更佳 ; 但是實際上牢裳,使用TCP
協議通信時逢防,信號驅動IO
模型幾乎不會被采用,因為信號IO
在大量IO
操作時可能會因為信號隊列溢出導致沒法通知 蒲讯, SIGIO
信號是一種 Unix
信號忘朝,信號沒有附加信息,如果一個信號源有多種產生信號的原因判帮,信號接收者就無法確定究竟發(fā)生了什么局嘁,而 TCP socket
生產的信號事件有七種之多,這樣應用程序收到 SIGIO
晦墙,根本無從區(qū)分處理 悦昵; 但信號驅動IO
模型可以用在 UDP
通信上,因為UDP 只有一個數據請求事件偎痛,這也就意味著在正常情況下 UDP 進程只要捕獲 SIGIO
信號旱捧,就調用 read
系統(tǒng)調用讀取到達的數據,如果出現異常踩麦,就返回一個異常錯誤 枚赡;
這里需要注意的是信號驅動式
IO
模型依然是同步IO
,因為它雖然可以在等待數據的時候不被阻塞谓谦,也不會頻繁的輪詢贫橙,但是當數據就緒,內核信號通知后反粥,用戶進程依然要自己去讀取數據卢肃,在數據拷貝階段發(fā)生阻塞
異步IO(AIO)
異步IO
的系統(tǒng)調用需要操作系統(tǒng)內核來支持,目前只有Window
中的IOCP
實現了非常成熟的異步IO
機制 才顿;