偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

徹底搞懂 Select / Poll / Epoll,就這篇了!

開發(fā) 前端
假設(shè)此時(shí)客戶端發(fā)送了數(shù)據(jù),網(wǎng)卡接收到的數(shù)據(jù)塞到對(duì)應(yīng)的 socket 的接收隊(duì)列中,此時(shí) socket 知道來數(shù)據(jù)了,那如何喚醒 select 呢?

之前已經(jīng)把網(wǎng)絡(luò) I/O 相關(guān)要點(diǎn)都盤了,還剩 select/poll/epoll 這幾個(gè)區(qū)別沒說,這篇就來搞搞它們,并且是從完全理解原理的角度來區(qū)分它們。

本來是要上源碼的,但是感覺沒啥必要,身為應(yīng)用開發(fā)我覺得理解原理就行了,源碼反正看了就忘了,理解才是最重要!所以我就盡量避免代碼且用大白話來盤一盤這三個(gè)玩意。

話不多說,發(fā)車。

小思考

首先,我們知道 select/poll/epoll 是用來實(shí)現(xiàn)多路復(fù)用的,即一個(gè)線程利用它們即可 hold 住多個(gè) socket。

按照這個(gè)思路,線程不可被任何一個(gè)被管理的 Socket 阻塞,且任一個(gè) Socket 來數(shù)據(jù)之后都得告知 select/poll/epoll 線程。

想想看,這應(yīng)該如何實(shí)現(xiàn)呢?

我們拿 select 的邏輯來分析下

按照我們的理解,select 管理多個(gè) Socket 的模型如下圖所示:

這里要注意一下內(nèi)核態(tài)和用戶態(tài)的交互,用戶程序訪問不了內(nèi)核空間。

所以,我們調(diào)用 select 會(huì)把所有要管理的 socket 的 fd (文件描述符,Linux下皆為文件,簡單理解就是通過 fd 能找到這個(gè) socket)傳到內(nèi)核中。

此時(shí),要遍歷所有 socket,看看是否有感興趣的事件發(fā)生。如果沒有一個(gè) socket 有事件發(fā)生,那么 select 的線程就需要讓出 cpu 阻塞等待,這個(gè)等待可以是不設(shè)置超時(shí)時(shí)間的死等,也可以是設(shè)置 timeout 的有超時(shí)時(shí)間的等待。

假設(shè)此時(shí)客戶端發(fā)送了數(shù)據(jù),網(wǎng)卡接收到的數(shù)據(jù)塞到對(duì)應(yīng)的 socket 的接收隊(duì)列中,此時(shí) socket 知道來數(shù)據(jù)了,那如何喚醒 select 呢?

其實(shí)每個(gè) socket 有個(gè)屬于自己的睡眠隊(duì)列,select 會(huì)安排一個(gè)內(nèi)應(yīng),即在被管理的 socket 的睡眠隊(duì)列里面塞入一個(gè) entry。

當(dāng) socket 接收到網(wǎng)卡的數(shù)據(jù)后,就會(huì)去它的睡眠隊(duì)列里遍歷 entry,調(diào)用 entry 設(shè)置的 callback 方法,這個(gè) callback 方法里就能喚醒 select !

所以 select 在每個(gè)被它管理的 socket 的睡眠隊(duì)列里都塞入一個(gè)與它相關(guān)的 entry,這樣不論哪個(gè) socket 來數(shù)據(jù)了,它立馬就能被喚醒然后干活!

但是,select 的實(shí)現(xiàn)不太好,因?yàn)閱拘训?select 此時(shí)只知道來活了,并不知道具體是哪個(gè) socket 來數(shù)據(jù)了,所以只能傻傻地遍歷所有 socket ,看看到底是哪個(gè) scoket 來活了,然后把所有來活的 socket 封裝成事件返回。

這樣用戶程序就能獲得發(fā)生的事件,然后進(jìn)行 I/O 和業(yè)務(wù)處理了。

這就是 select 的實(shí)現(xiàn)邏輯,理解起來應(yīng)該不難。

這里再提一嘴 select 的限制,因?yàn)楸还芾淼?socket fd 需要從用戶空間拷貝到內(nèi)核空間,為了控制拷貝的大小而做了限制,即每個(gè) select 能拷貝的 fds 集合大小只有1024。

然后要改的話只能修改宏..再重新編譯內(nèi)核。網(wǎng)上很多文章都是這樣說的,但是(沒錯(cuò)有個(gè)但是)。

我看了一篇文章,確實(shí)有這個(gè)宏,值也是 1024,但內(nèi)核根本沒有限制 fds 集合的大小。然后托人問了個(gè)內(nèi)核大佬,大佬說內(nèi)核確實(shí)沒做限制,glibc那層做了。

所以..重新編譯內(nèi)核?那篇文章放文末。

poll

poll 這玩意相比于 select 主要就是優(yōu)化了 fds 的結(jié)構(gòu),不再是 bit 數(shù)組了,而是一個(gè)叫 pollfd 的玩意,反正就是不用管啥 1024 的限制了。

不過現(xiàn)在也沒人用 poll,我就不多說了。

epoll

這個(gè)就是重點(diǎn)了。

相信看了 select 的實(shí)現(xiàn),我們稍微思考下,就能想出幾個(gè)可以優(yōu)化的點(diǎn)。

比如,為什么每次 select 需要把監(jiān)控的 fds 傳輸?shù)絻?nèi)核里?不能在內(nèi)核里維護(hù)個(gè)?

為什么 socket 只喚醒 select,不能告訴它是哪個(gè) socket 來數(shù)據(jù)了?

epoll 主要就是基于上面兩點(diǎn)做了優(yōu)化。

首先,搞了個(gè)叫 epoll_ctl 的方法,這方法就是用來管理維護(hù) epoll 所監(jiān)控的哪些 socket。

如果你的 epoll 要新加一個(gè) socket 來管理,那就調(diào)用 epoll_ctl,要?jiǎng)h除一個(gè) socket 也調(diào)用 epoll_ctl,通過不同的入?yún)砜刂圃鰟h改。

這樣,在內(nèi)核里面就維護(hù)了此 epoll 管理的 socket 集合,這樣就不用每次調(diào)用的時(shí)候都得把所有管理的 fds 拷貝到內(nèi)核了。

對(duì)了,這個(gè) socket 集合是用紅黑樹實(shí)現(xiàn)的。

然后和 select 類似,每個(gè) socket 的睡眠隊(duì)列里都會(huì)加個(gè) entry,當(dāng)每個(gè) socket 來數(shù)據(jù)之后,同樣也會(huì)調(diào)用 entry 對(duì)應(yīng)的 callback。

與 select 不同的是,引入了一個(gè) ready_list 雙向鏈表,callback 里面會(huì)把當(dāng)前的 socket 加入到 ready_list 然后喚醒 epoll。

這樣被喚醒的 epoll 只需要遍歷 ready_list 即可,這個(gè)鏈表里一定是有數(shù)據(jù)可讀的 socket,相比于 select 就不會(huì)做無用的遍歷了。

同時(shí)收集到的可讀的 fd 按理是要拷貝到用戶空間的,這里又做了個(gè)優(yōu)化,利用了 mmp,讓用戶空間和內(nèi)核空間映射到同一塊內(nèi)存中,這樣就避免了拷貝。

完美啊~

這就是 epoll 基于 select 所作的優(yōu)化,還有一些差別沒細(xì)說,比如 epoll 是阻塞睡眠在一個(gè) single_epoll_wait_list 而不是 socket 的睡眠隊(duì)列等等,我就不提了,理解上面的這些已經(jīng)夠了。

ET&LT

都談到 epoll 了,避免不了要扯扯 ET 和 LT 兩個(gè)模式。

ET,邊沿觸發(fā)。

按照上面的邏輯就是 epoll 遍歷 ready_list 的時(shí)候,會(huì)把 socket 從 ready_list 里面移除,然后讀取這個(gè) scoket 的事件。

而 LT,水平觸發(fā),有點(diǎn)不一樣。

在這個(gè)模式下 epoll 遍歷 ready_list 的時(shí)候,會(huì)把 socket 從 ready_list 里面移除,然后讀取這個(gè) scoket 的事件,如果這個(gè) socket 返回了感興趣的事件,那么當(dāng)前這個(gè) socket 會(huì)再被加入到 ready_list 中,這樣下次調(diào)用 epoll_wait 的時(shí)候,還能拿到這個(gè) socket。

這就是這兩者最本質(zhì)的區(qū)別了。

看到這有人會(huì)問,這兩種模式的使用會(huì)造成哪種不一樣的結(jié)果?

如果此時(shí)一個(gè)客戶端同時(shí)發(fā)來了 5 個(gè)數(shù)據(jù)包,按正常的邏輯,只需要喚醒一次 epoll ,把當(dāng)前 socket 加一次到 ready_list 就行了,不需要加 5 次。然后用戶程序可以把 socket 接收隊(duì)列的所有數(shù)據(jù)包都讀完。

但假設(shè)用戶程序就讀了一個(gè)包,然后處理報(bào)錯(cuò)了,后面不讀了,那后面的 4 個(gè)包咋辦?

如果是 ET 模式,就讀不了了,因?yàn)闆]有把 socket 加入到 ready_list 的觸發(fā)條件了。除非這個(gè)客戶端發(fā)了新的數(shù)據(jù)包過來,這樣才會(huì)再把當(dāng)前 socket 加入到 ready_list,在新包過來之前,這 4 個(gè)數(shù)據(jù)包都不會(huì)被讀到。

而 LT 模式不一樣,因?yàn)槊看巫x完有感興趣的事件發(fā)生之后,會(huì)把當(dāng)前 socket 再加入到 ready_list,所以下次肯定能讀到這個(gè) socket,所以后面的 4 個(gè)數(shù)據(jù)包會(huì)被訪問到,不論客戶端是否發(fā)送新包。

至此,我想你應(yīng)該理解什么是 ET ,什么是 LT 了,而不用對(duì)著一些什么狀態(tài)變更觸發(fā)這些不易理解的名詞而發(fā)暈。

最后

好了,今天的分析到此完畢,我個(gè)人覺得對(duì) select/poll/epoll 的理解到這個(gè)程度就差不多了,當(dāng)然還有很多細(xì)節(jié),需要自行去看源碼探究,問我我也不懂,這些都是閱讀網(wǎng)上的源碼分析文章得出的結(jié)論。

我也不建議讀的那么深,畢竟人的精力有限對(duì)吧,有涉及到相關(guān)底層優(yōu)化的時(shí)候,再去研究也不遲。

我是yes,從一點(diǎn)點(diǎn)到億點(diǎn)點(diǎn),我們下篇見。

參考:

https://blog.csdn.net/dog250/article/details/105896693(select真的受1024限制嗎?)

https://blog.csdn.net/dog250/article/details/50528373


責(zé)任編輯:武曉燕 來源: yes的練級(jí)攻略
相關(guān)推薦

2020-11-04 07:49:04

Select

2019-07-31 15:56:57

Jvm虛擬機(jī)Content

2022-04-07 13:02:53

前端緩存

2025-01-07 00:07:17

2021-05-31 06:50:47

SelectPoll系統(tǒng)

2024-07-05 11:01:13

2020-09-09 12:55:28

Nginx高并發(fā)性能

2020-09-10 09:31:34

Nginx HTTP代理服務(wù)器

2022-06-03 10:52:55

selectpolepoll

2018-10-12 09:42:00

分布式鎖 Java多線

2020-07-20 10:20:30

this前端代碼

2022-09-19 18:49:01

偵聽器異步組件

2024-09-27 13:09:30

2017-12-05 17:44:31

機(jī)器學(xué)習(xí)CNN卷積層

2020-10-14 08:50:38

搞懂 Netty 線程

2025-05-06 01:14:00

系統(tǒng)編程響應(yīng)式

2025-06-30 00:32:43

策略模式算法MyBatis

2025-04-21 04:00:00

2022-07-01 13:38:48

霧計(jì)算邊緣計(jì)算

2020-11-12 10:37:29

微服務(wù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)