Redis 网络模型 -- 阻塞非阻塞IO、IO多路复用、epoll详解
引言
本篇前半部分屬于知識(shí)點(diǎn),后半部分的[手撕面答環(huán)節(jié)],以問(wèn)題展開(kāi),應(yīng)對(duì)面試場(chǎng)景作答,盡量簡(jiǎn)短,可以在學(xué)習(xí)了前置知識(shí)后,嘗試自己作答復(fù)述喔。
本篇先簡(jiǎn)單介紹常見(jiàn)的IO模型,還未深入具體Redis中的應(yīng)用,可以把這節(jié)當(dāng)做【操作系統(tǒng)】來(lái)啃hhh
🎨本篇腦圖速覽
🎯常見(jiàn)的幾種網(wǎng)絡(luò)模型?
阻塞 IO
-
過(guò)程 1:應(yīng)用程序想要去讀取數(shù)據(jù),他是無(wú)法直接去讀取磁盤(pán)數(shù)據(jù)的,他需要先到內(nèi)核里邊去等待內(nèi)核操作硬件拿到數(shù)據(jù),這個(gè)等待數(shù)據(jù)就緒的過(guò)程便是過(guò)程1。
-
過(guò)程 2:內(nèi)核態(tài)準(zhǔn)備好了,開(kāi)始拷貝數(shù)據(jù)給用戶(hù)緩沖區(qū),便是過(guò)程2。
用戶(hù)去讀取數(shù)據(jù)時(shí),會(huì)去先發(fā)起 recvform 一個(gè)命令,去嘗試從內(nèi)核上加載數(shù)據(jù),如果內(nèi)核沒(méi)有數(shù)據(jù),那么用戶(hù)就會(huì)等待,此時(shí)內(nèi)核會(huì)去從硬件上讀取數(shù)據(jù),內(nèi)核讀取數(shù)據(jù)之后,會(huì)把數(shù)據(jù)拷貝到用戶(hù)態(tài),并且返回 ok,整個(gè)過(guò)程,都是阻塞等待的,這就是阻塞 IO
也就是兩個(gè)過(guò)程都阻塞的話(huà),便是阻塞IO
總結(jié)如下:
顧名思義,阻塞 IO 就是兩個(gè)階段都必須阻塞等待:
階段一:
- 用戶(hù)進(jìn)程嘗試讀取數(shù)據(jù)(比如網(wǎng)卡數(shù)據(jù))
- 此時(shí)數(shù)據(jù)尚未到達(dá),內(nèi)核需要等待數(shù)據(jù)
- 此時(shí)用戶(hù)進(jìn)程也處于阻塞狀態(tài)
階段二:
- 數(shù)據(jù)到達(dá)并拷貝到內(nèi)核緩沖區(qū),代表已就緒
- 將內(nèi)核數(shù)據(jù)拷貝到用戶(hù)緩沖區(qū)
- 拷貝過(guò)程中,用戶(hù)進(jìn)程依然阻塞等待
- 拷貝完成,用戶(hù)進(jìn)程解除阻塞,處理數(shù)據(jù)
流程圖
非阻塞 IO
顧名思義,非阻塞 IO 的 recvfrom 操作會(huì)立即返回結(jié)果而不是阻塞用戶(hù)進(jìn)程。
階段一:
- 用戶(hù)進(jìn)程嘗試讀取數(shù)據(jù)(比如網(wǎng)卡數(shù)據(jù))
- 此時(shí)數(shù)據(jù)尚未到達(dá),內(nèi)核需要等待數(shù)據(jù)
- 返回異常給用戶(hù)進(jìn)程
- 用戶(hù)進(jìn)程收到 error 后,再次嘗試讀取【忙輪詢(xún)】
- 循環(huán)往復(fù),直到數(shù)據(jù)就緒
階段二:
- 將內(nèi)核數(shù)據(jù)拷貝到用戶(hù)緩沖區(qū)
- 拷貝過(guò)程中,用戶(hù)進(jìn)程依然阻塞等待
- 拷貝完成,用戶(hù)進(jìn)程解除阻塞,處理數(shù)據(jù)
可以看到,非阻塞 IO 模型中,用戶(hù)進(jìn)程在第一個(gè)階段是非阻塞,第二個(gè)階段是阻塞狀態(tài)。雖然是非阻塞,但性能并沒(méi)有得到提高。而且忙等機(jī)制會(huì)導(dǎo)致 CPU 空轉(zhuǎn),CPU 使用率暴增。
信號(hào)驅(qū)動(dòng)
信號(hào)驅(qū)動(dòng) IO 是與內(nèi)核建立 SIGIO 的信號(hào)關(guān)聯(lián)并設(shè)置回調(diào),當(dāng)內(nèi)核有 FD 就緒時(shí),會(huì)發(fā)出 SIGIO 信號(hào)通知用戶(hù),期間用戶(hù)應(yīng)用可以執(zhí)行其它業(yè)務(wù),無(wú)需阻塞等待。
階段一:
- 用戶(hù)進(jìn)程調(diào)用 sigaction ,注冊(cè)信號(hào)處理函數(shù)
- 內(nèi)核返回成功,開(kāi)始監(jiān)聽(tīng) FD
- 用戶(hù)進(jìn)程不阻塞等待,可以執(zhí)行其它業(yè)務(wù)
- 當(dāng)內(nèi)核數(shù)據(jù)就緒后,回調(diào)用戶(hù)進(jìn)程的 SIGIO 處理函數(shù)
階段二:
- 收到 SIGIO 回調(diào)信號(hào)
- 調(diào)用 recvfrom ,讀取
- 內(nèi)核將數(shù)據(jù)拷貝到用戶(hù)空間
- 用戶(hù)進(jìn)程處理數(shù)據(jù)
缺點(diǎn)
當(dāng)有大量 IO 操作時(shí),信號(hào)較多,SIGIO 處理函數(shù)不能及時(shí)處理可能導(dǎo)致信號(hào)隊(duì)列溢出,而且內(nèi)核空間與用戶(hù)空間的頻繁信號(hào)交互性能也較低。
異步 IO
這種方式,不僅僅是用戶(hù)態(tài)在試圖讀取數(shù)據(jù)后,不阻塞,而且當(dāng)內(nèi)核的數(shù)據(jù)準(zhǔn)備完成后,也不會(huì)阻塞
兩個(gè)過(guò)程都不阻塞
他會(huì)由內(nèi)核將所有數(shù)據(jù)處理完成后,由內(nèi)核將數(shù)據(jù)寫(xiě)入到用戶(hù)態(tài)中,然后才算完成,所以性能極高,不會(huì)有任何阻塞,全部都由內(nèi)核完成,可以看到,異步 IO 模型中,用戶(hù)進(jìn)程在兩個(gè)階段都是非阻塞狀態(tài)。
缺點(diǎn)
得做好限流,不然無(wú)腦的給內(nèi)核去干,相當(dāng)于領(lǐng)導(dǎo)不管用戶(hù)死活,一股腦塞
🎯Java中常見(jiàn)的IO模型
BIO
上文的阻塞IO
NIO
上文的非阻塞IO
AIO
其實(shí)就是上文的異步模型
🎯什么是IO多路復(fù)用
定義 & 流程
當(dāng)用戶(hù)進(jìn)程調(diào)用了select,那么整個(gè)進(jìn)程會(huì)被阻塞,而同時(shí),內(nèi)核會(huì)"監(jiān)視"所有select負(fù)責(zé)的socket,當(dāng)任何一個(gè)socket中的數(shù)據(jù)準(zhǔn)備好了,select就會(huì)返回。這個(gè)時(shí)候用戶(hù)進(jìn)程再調(diào)用read操作,將數(shù)據(jù)從內(nèi)核拷貝到用戶(hù)進(jìn)程。
這個(gè)模型和阻塞IO的模型其實(shí)并沒(méi)有太大的不同,事實(shí)上還更差一些。因?yàn)檫@里需要使用兩個(gè)系統(tǒng)調(diào)用(select和recvfrom),而阻塞IO只調(diào)用了一個(gè)系統(tǒng)調(diào)用(recvfrom)。
- 但是,用select的優(yōu)勢(shì)在于它可以同時(shí)處理多個(gè)連接。所以,如果系統(tǒng)的連接數(shù)不是很高的話(huà),使用select/epoll的web server不一定比使用多線(xiàn)程的阻塞IO的web server性能更好,可能延遲還更大;select/epoll的優(yōu)勢(shì)并不是對(duì)單個(gè)連接能處理得更快,而是在于能處理更多的連接。
🎯IO多路復(fù)用的三種實(shí)現(xiàn)方式
目前流程的多路復(fù)用 IO 實(shí)現(xiàn)主要包括四種: select、poll、epoll、kqueue。下表是他們的一些重要特性的比較:
| select | 較高 | Reactor | windows/Linux | 支持,Reactor 模式 (反應(yīng)器設(shè)計(jì)模式)。Linux 操作系統(tǒng)的 kernels 2.4 內(nèi)核版本之前,默認(rèn)使用 select;而目前 windows 下對(duì)同步 IO 的支持,都是 select 模型 |
| poll | 較高 | Reactor | Linux | Linux 下的 JAVA NIO 框架,Linux kernels 2.6 內(nèi)核版本之前使用 poll 進(jìn)行支持。也是使用的 Reactor 模式 |
| epoll | 高 | Reactor/Proactor | Linux | Linux kernels 2.6 內(nèi)核版本及以后使用 epoll 進(jìn)行支持;Linux kernels 2.6 內(nèi)核版本之前使用 poll 進(jìn)行支持;另外一定注意,由于 Linux 下沒(méi)有 Windows 下的 IOCP 技術(shù)提供真正的 異步 IO 支持,所以 Linux 下使用 epoll 模擬異步 IO |
| kqueue | 高 | Proactor | Linux | 目前 JAVA 的版本不支持 |
select
select 是 Linux 最早的 I/O 多路復(fù)用技術(shù):
linux 中,一切皆文件,socket 也不例外,我們把需要處理的數(shù)據(jù)封裝成 FD,然后在用戶(hù)態(tài)時(shí)創(chuàng)建一個(gè) fd_set 的集合(這個(gè)集合的大小是要監(jiān)聽(tīng)的那個(gè) FD 的最大值 + 1,但是大小整體是有限制的 ),這個(gè)集合的長(zhǎng)度大小是有限制的,同時(shí)在這個(gè)集合中,標(biāo)明出來(lái)我們要控制哪些數(shù)據(jù)。
具體流程
用戶(hù)態(tài) :
內(nèi)核態(tài):
🎈源碼&流程
🎐不足之處
需要進(jìn)行 2 次「遍歷」文件描述符集合,一次是在內(nèi)核態(tài)里,一個(gè)次是在用戶(hù)態(tài)里 ,而且還會(huì)發(fā)生 2 次「拷貝」文件描述符集合,先從用戶(hù)空間傳入內(nèi)核空間,由內(nèi)核修改后,再傳出到用戶(hù)空間中。
poll
poll 模式對(duì) select 模式做了簡(jiǎn)單改進(jìn),但性能提升不明顯。
具體流程:
與 select 對(duì)比
大小方面:
- select 模式中的 fd_set 大小固定為 1024,而 pollfd 在內(nèi)核中采用鏈表,理論上無(wú)上限,但實(shí)際上不能這么做,因?yàn)榈谋O(jiān)聽(tīng) FD 越多,每次遍歷消耗時(shí)間也越久,性能反而會(huì)下降
🎈epoll
epoll 模式是對(duì) select 和 poll 的改進(jìn),它提供了三個(gè)函數(shù):eventpoll 、epoll_ctl 、epoll_wait
-
eventpoll 函數(shù)內(nèi)部包含了兩個(gè)東西 :
- 紅黑樹(shù) :用來(lái)記錄所有的 fd
- 鏈表 : 記錄已就緒的 fd 、
-
epoll_ctl 函數(shù) ,將要監(jiān)聽(tīng)的 fd 添加到 紅黑樹(shù) 上去,并且給每個(gè) fd 綁定一個(gè)監(jiān)聽(tīng)函數(shù),當(dāng) fd 就緒時(shí)就會(huì)被觸發(fā),這個(gè)監(jiān)聽(tīng)函數(shù)的操作就是 將這個(gè) fd 添加到 鏈表中去。
-
epoll_wait 函數(shù),就緒等待。一開(kāi)始,用戶(hù)態(tài) buffer 中創(chuàng)建一個(gè)空的 events 數(shù)組,當(dāng)就緒之后,我們的回調(diào)函數(shù)會(huì)把 fd 添加到鏈表中去
- 當(dāng)函數(shù)被調(diào)用的時(shí)候,會(huì)去檢查鏈表(當(dāng)然這個(gè)過(guò)程需要參考配置的等待時(shí)間,可以等一定時(shí)間,也可以一直等)
- 如果鏈表中沒(méi)有 fd ,則 fd 會(huì)從紅黑樹(shù)被添加到鏈表中,此時(shí)再將鏈表中的的 fd 復(fù)制到用戶(hù)態(tài)的空 events中,并且返回對(duì)應(yīng)的操作數(shù)量,用戶(hù)態(tài)此時(shí)收到響應(yīng)后,會(huì)從 events 中拿到已經(jīng)準(zhǔn)備好的數(shù)據(jù),在調(diào)用 讀方法 去拿數(shù)據(jù)。
- 當(dāng)函數(shù)被調(diào)用的時(shí)候,會(huì)去檢查鏈表(當(dāng)然這個(gè)過(guò)程需要參考配置的等待時(shí)間,可以等一定時(shí)間,也可以一直等)
🎈🎈總結(jié)
select 模式存在的三個(gè)問(wèn)題:
- 能監(jiān)聽(tīng)的 FD 最大不超過(guò) 1024
- 每次 select 都需要把所有要監(jiān)聽(tīng)的 FD 都拷貝到內(nèi)核空間
- 每次都要遍歷所有 FD 來(lái)判斷就緒狀態(tài)
poll 模式的問(wèn)題:
- poll 利用鏈表解決了 select 中監(jiān)聽(tīng) FD 上限的問(wèn)題,但依然要遍歷所有 FD,如果監(jiān)聽(tīng)較多,性能會(huì)下降
epoll 模式中如何解決這些問(wèn)題的?
- 基于 epoll 實(shí)例中的紅黑樹(shù)保存要監(jiān)聽(tīng)的 FD,理論上無(wú)上限 ,而且增刪改查效率都非常高,性能不會(huì)隨監(jiān)聽(tīng)
- 每個(gè) FD 只需要執(zhí)行一次 epoll_ctl 添加到紅黑樹(shù),以后每次 epol_wait 無(wú)需傳遞任何參數(shù),無(wú)需重復(fù)拷貝 FD 到內(nèi)核空間
- 利用 ep_poll_callback 機(jī)制來(lái)監(jiān)聽(tīng) FD 狀態(tài),無(wú)需遍歷所有 FD,因此性能不會(huì)隨監(jiān)聽(tīng)的 FD 數(shù)量增多而下降
🎯邊緣觸發(fā)和水平觸發(fā)
epoll 支持兩種事件觸發(fā)模式,分別是邊緣觸發(fā)(edge-triggered,ET)和水平觸發(fā)(level-triggered,LT)。
-
使用邊緣觸發(fā)模式時(shí),當(dāng)被監(jiān)控的 Socket 描述符上有可讀事件發(fā)生時(shí),服務(wù)器端只會(huì)從 epoll_wait 中蘇醒一次,即使進(jìn)程沒(méi)有調(diào)用 read 函數(shù)從內(nèi)核讀取數(shù)據(jù),也依然只蘇醒一次,因此我們程序要保證一次性將內(nèi)核緩沖區(qū)的數(shù)據(jù)讀取完;
-
使用水平觸發(fā)模式時(shí),當(dāng)被監(jiān)控的 Socket 上有可讀事件發(fā)生時(shí),服務(wù)器端不斷地從 epoll_wait 中蘇醒,直到內(nèi)核緩沖區(qū)數(shù)據(jù)被 read 函數(shù)讀完才結(jié)束,目的是告訴我們有數(shù)據(jù)需要讀取;
這個(gè)過(guò)程是用戶(hù)空間去讀內(nèi)核空間
水平觸發(fā)的意思是只要滿(mǎn)足事件的條件,比如內(nèi)核中有數(shù)據(jù)需要讀,就一直不斷地把這個(gè)事件傳遞給用戶(hù);而邊緣觸發(fā)的意思是只有第一次滿(mǎn)足條件的時(shí)候才觸發(fā),之后就不會(huì)再傳遞同樣的事件了。
如果使用水平觸發(fā)模式,當(dāng)內(nèi)核通知文件描述符可讀寫(xiě)時(shí),接下來(lái)還可以繼續(xù)去檢測(cè)它的狀態(tài),看它是否依然可讀或可寫(xiě)。所以在收到通知后,沒(méi)必要一次執(zhí)行盡可能多的讀寫(xiě)操作。
邊緣觸發(fā)注意點(diǎn)
如果使用邊緣觸發(fā)模式,I/O 事件發(fā)生時(shí)只會(huì)通知一次,而且我們不知道到底能讀寫(xiě)多少數(shù)據(jù),所以在收到通知后應(yīng)盡可能地讀寫(xiě)數(shù)據(jù),以免錯(cuò)失讀寫(xiě)的機(jī)會(huì)。
因此,我們會(huì)循環(huán)從文件描述符讀寫(xiě)數(shù)據(jù)【圖中的④操作使用循環(huán)】,那么如果文件描述符是阻塞的,沒(méi)有數(shù)據(jù)可讀寫(xiě)時(shí),進(jìn)程會(huì)阻塞在讀寫(xiě)函數(shù)那里,程序就沒(méi)辦法繼續(xù)往下執(zhí)行。
所以,邊緣觸發(fā)模式一般和非阻塞 I/O 搭配使用,程序會(huì)一直執(zhí)行 I/O 操作,直到系統(tǒng)調(diào)用(如 read 和 write)返回錯(cuò)誤,錯(cuò)誤類(lèi)型為 EAGAIN 或 EWOULDBLOCK。
一般來(lái)說(shuō),邊緣觸發(fā)的效率比水平觸發(fā)的效率要高,因?yàn)檫吘売|發(fā)可以減少 epoll_wait 的系統(tǒng)調(diào)用次數(shù),系統(tǒng)調(diào)用也是有一定的開(kāi)銷(xiāo)的的,畢竟也存在上下文的切換。
select/poll 只有水平觸發(fā)模式,epoll 默認(rèn)的觸發(fā)模式是水平觸發(fā),但是可以根據(jù)應(yīng)用場(chǎng)景設(shè)置為邊緣觸發(fā)模式。
🍿🍿🍿手撕面答環(huán)節(jié) -- 這是一條分割線(xiàn)
劃掉的部分屬于melo復(fù)述時(shí),發(fā)送的疏漏之處/答錯(cuò)的地方hhh
🍔select,poll,epoll的區(qū)別
select
用戶(hù)注冊(cè)了自己需要監(jiān)聽(tīng)的設(shè)備,記錄在一個(gè)fd數(shù)組里邊,拷貝給內(nèi)核態(tài)服務(wù)端,服務(wù)端那邊若準(zhǔn)備好了,會(huì)修改fd數(shù)組中對(duì)應(yīng)設(shè)備的位置,值改為1,并且把整個(gè)fd數(shù)組拷貝回用戶(hù)態(tài)
實(shí)際上服務(wù)端還要遍歷一遍fd數(shù)組,標(biāo)記就緒的fd為1,拷貝回用戶(hù)態(tài)
用戶(hù)態(tài)再遍歷一遍fd數(shù)組,找到其中值為1的,說(shuō)明準(zhǔn)備好了,可以開(kāi)始拷貝了。
不足之處
涉及到多次拷貝,用戶(hù)態(tài)和內(nèi)核態(tài)的切換
poll
跟select的區(qū)別主要在于,不是用fd數(shù)組了,而是用一個(gè)鏈表,理論上可以無(wú)限節(jié)點(diǎn),但本質(zhì)上,節(jié)點(diǎn)數(shù)量越多,效率自然隨著降低,有沒(méi)有能夠解決這種節(jié)點(diǎn)數(shù)影響效率的限制呢?這個(gè)時(shí)候epoll就出來(lái)了,紅黑樹(shù)。
更具體一點(diǎn)是,用戶(hù)態(tài)仍然是fd數(shù)組,轉(zhuǎn)到內(nèi)核態(tài)才變?yōu)殒湵泶鎯?chǔ)
epoll
把要監(jiān)聽(tīng)的設(shè)備,都注冊(cè)到一棵紅黑樹(shù)上邊,并給每個(gè)節(jié)點(diǎn)綁定監(jiān)聽(tīng)函數(shù),但服務(wù)端準(zhǔn)備就緒時(shí),會(huì)觸發(fā)監(jiān)聽(tīng)函數(shù),把該節(jié)點(diǎn)拷貝到fd數(shù)組上邊【是就緒鏈表上邊】,并且返回給用戶(hù)態(tài)【注意只返回準(zhǔn)備好了的設(shè)備,這是跨時(shí)代的進(jìn)步】
優(yōu)點(diǎn)
select/poll 每次操作時(shí)都傳入整個(gè) socket 集合給內(nèi)核,而 epoll 因?yàn)樵趦?nèi)核維護(hù)了紅黑樹(shù),可以保存所有待檢測(cè)的 socket ,所以只需要傳入一個(gè)待檢測(cè)的 socket,減少了內(nèi)核和用戶(hù)空間大量的數(shù)據(jù)拷貝和內(nèi)存分配。
🍔🎐邊緣觸發(fā)為何建議搭配非阻塞IO?
多路復(fù)用 API 返回的事件并不一定可讀寫(xiě)的【select() 可能會(huì)將一個(gè) socket 文件描述符報(bào)告為 "準(zhǔn)備讀取",而后續(xù)的讀取塊卻沒(méi)有。例如,當(dāng)數(shù)據(jù)已經(jīng)到達(dá),但經(jīng)檢查后發(fā)現(xiàn)有錯(cuò)誤的校驗(yàn)和而被丟棄時(shí),就會(huì)發(fā)生這種情況】
虛晃一槍,以為準(zhǔn)備好了要給你數(shù)據(jù)了,但這時(shí)被丟棄了【又變成還沒(méi)準(zhǔn)備好的狀態(tài)】,我們還傻傻的一直在等待讀取
如果使用阻塞 I/O, 那么在調(diào)用 read/write 時(shí)則會(huì)發(fā)生程序阻塞,
非阻塞 I/O的話(huà),會(huì)忙等輪詢(xún),直到系統(tǒng)調(diào)用(如 read 和 write)返回錯(cuò)誤,錯(cuò)誤類(lèi)型為 EAGAIN 或 EWOULDBLOCK。
阻塞IO:當(dāng)你去讀一個(gè)阻塞的文件描述符時(shí),如果在該文件描述符上沒(méi)有數(shù)據(jù)可讀,那么它會(huì)一直阻塞(通俗一點(diǎn)就是一直卡在調(diào)用函數(shù)那里),直到有數(shù)據(jù)可讀。當(dāng)你去寫(xiě)一個(gè)阻塞的文件描述符時(shí),如果在該文件描述符上沒(méi)有空間(通常是緩沖區(qū))可寫(xiě),那么它會(huì)一直阻塞,直到有空間可寫(xiě)。
非阻塞IO:當(dāng)你去讀寫(xiě)一個(gè)非阻塞的文件描述符時(shí),不管可不可以讀寫(xiě),它都會(huì)立即返回,返回成功說(shuō)明讀寫(xiě)操作完成了,返回失敗會(huì)設(shè)置相應(yīng)errno狀態(tài)碼,根據(jù)這個(gè)errno可以進(jìn)一步執(zhí)行其他處理。它不會(huì)像阻塞IO那樣,卡在那里不動(dòng)!!!
另一種答案
由于ET模式下,需要while循環(huán)調(diào)用read和wirte,直到最后返回特定的錯(cuò)誤類(lèi)型才退出循環(huán)。
如果采用非阻塞IO,則可能會(huì)在最后一次本應(yīng)該跳出循環(huán)的read調(diào)用阻塞住。
🍔epoll的ET和LT有什么區(qū)別
ET:edge trigger 邊緣觸發(fā),指的是當(dāng)socket準(zhǔn)備好了,服務(wù)端只蘇醒一次,所以用戶(hù)緩沖區(qū)要一次性把內(nèi)核緩沖區(qū)讀完,nginx就是采用的ET
LT:level-trigger 水平觸發(fā),socket準(zhǔn)備好了,服務(wù)端會(huì)不斷蘇醒,直到用戶(hù)緩沖區(qū)把內(nèi)核緩沖區(qū)讀完了,redis就是采用的LT
🍔邊緣觸發(fā)如何保證數(shù)據(jù)讀完
while循環(huán)讀寫(xiě),直到最后一次返回特定的錯(cuò)誤類(lèi)型【EAGAIN錯(cuò)誤】
🍔ET模式下的accept問(wèn)題
在某一時(shí)刻,有多個(gè)連接同時(shí)到達(dá),服務(wù)器的 TCP 就緒隊(duì)列瞬間積累多個(gè)就緒連接,由于是邊緣觸發(fā)模式,epoll 只會(huì)通知一次,accept 只處理一個(gè)連接,導(dǎo)致 TCP 就緒隊(duì)列中剩下的連接都得不到處理。在這種情形下,我們應(yīng)該如何有效的處理呢?
解決的方法是:解決辦法是用 while 循環(huán)包住 accept 調(diào)用,處理完 TCP 就緒隊(duì)列中的所有連接后再退出循環(huán)。
如何知道是否處理完就緒隊(duì)列中的所有連接呢?
- accept 返回 -1 并且 errno 設(shè)置為 EAGAIN 就表示所有連接都處理完。
🍔epoll讀到一半又有新事件來(lái)了怎么辦?
避免在主進(jìn)程epoll再次監(jiān)聽(tīng)到同一個(gè)可讀事件,可以把對(duì)應(yīng)的描述符設(shè)置為EPOLL_ONESHOT,效果是監(jiān)聽(tīng)到一次事件后就將對(duì)應(yīng)的描述符從監(jiān)聽(tīng)集合中移除,也就不會(huì)再被追蹤到。讀完之后可以再把對(duì)應(yīng)的描述符重新手動(dòng)加上。
總結(jié)
以上是生活随笔為你收集整理的Redis 网络模型 -- 阻塞非阻塞IO、IO多路复用、epoll详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 股豆网:2019全国高校名单公布共计29
- 下一篇: m分别使用BP神经网络和GRNN网络进行