国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll

這篇具有很好參考價(jià)值的文章主要介紹了【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

所有通過(guò)捷徑所獲取的快樂(lè),無(wú)論是金錢(qián)、性還是名望,最終都會(huì)給自己帶來(lái)痛苦
【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器


一、五種IO模型

1.什么是高效的IO?(降低等待的時(shí)間比重)

1.
后端服務(wù)器最常用的網(wǎng)絡(luò)IO設(shè)計(jì)模式其實(shí)就是Reactor,也稱為反應(yīng)堆模式,Reactor是單進(jìn)程,單線程的,但他能夠處理多客戶端向服務(wù)器發(fā)起的網(wǎng)絡(luò)IO請(qǐng)求,正因?yàn)樗菃螆?zhí)行流,所以他的成本就不高,CPU和內(nèi)存這樣的資源占用率就會(huì)低,降低服務(wù)器性能的開(kāi)銷,提高服務(wù)器性能。
而多進(jìn)程多線程方案的服務(wù)器,缺點(diǎn)相比于Reactor就很明顯了,在高并發(fā)的場(chǎng)景下,服務(wù)器會(huì)面臨著大量的連接請(qǐng)求,每個(gè)線程都需要自己的內(nèi)存空間,堆棧,自己的內(nèi)核數(shù)據(jù)結(jié)構(gòu),所以大量的線程所造成的資源消耗會(huì)降低服務(wù)器的性能,多線程還會(huì)進(jìn)行線程的上下文切換,也就是執(zhí)行流級(jí)別的切換,每一次切換都需要保存和恢復(fù)線程的上下文信息,這會(huì)消耗CPU的時(shí)間,頻繁的上下文切換也會(huì)降低服務(wù)器的性能。前面的這些問(wèn)題都是針對(duì)于服務(wù)器來(lái)說(shuō)的,對(duì)于程序員來(lái)說(shuō),多執(zhí)行流的服務(wù)器最惡心的就是調(diào)試和找bug了,所以多執(zhí)行流的服務(wù)器生態(tài)比較差,排查問(wèn)題更加的困難,服務(wù)器不好維護(hù),同時(shí)由于多執(zhí)行流可能同時(shí)訪問(wèn)臨界資源,所以服務(wù)器的安全性也比較低,可能產(chǎn)生資源競(jìng)爭(zhēng),數(shù)據(jù)損壞等問(wèn)題。

2.
談完Reactor這種模式的好處之后,接下來(lái)理解一下什么是高效的IO,只有真正理解了IO,我們才能理解Reactor這種模式。
IO這件事我們并不陌生,在我們自己的電腦內(nèi)部其實(shí)就無(wú)時(shí)不刻的在進(jìn)行著IO,因?yàn)轳T諾依曼體系已經(jīng)決定了計(jì)算機(jī)是要無(wú)時(shí)不刻進(jìn)行IO的,從存儲(chǔ)設(shè)備中拿取數(shù)據(jù)到內(nèi)存,將處理結(jié)果再返回至內(nèi)存,從網(wǎng)絡(luò)IO角度來(lái)講,我們的計(jì)算機(jī)要從網(wǎng)卡這樣的硬件中將數(shù)據(jù)拿到計(jì)算機(jī)內(nèi)存,將數(shù)據(jù)處理完畢之后,可能還要再將數(shù)據(jù)從網(wǎng)卡發(fā)出去,這其實(shí)也是IO的過(guò)程,所以對(duì)于計(jì)算機(jī)來(lái)說(shuō)IO是非常常見(jiàn)的一件事情。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器
3.
但上面對(duì)IO的理解還是不夠深刻,以前在學(xué)習(xí)TCP網(wǎng)絡(luò)套接字編程時(shí),我們就談到過(guò),IO其實(shí)就是進(jìn)行拷貝,例如send時(shí),其實(shí)是把自己buffer緩沖區(qū)中的數(shù)據(jù)拷貝到內(nèi)核的sk_buff中,recv時(shí),其實(shí)是把內(nèi)核的接收緩沖區(qū)中的數(shù)據(jù)拷貝到自己在應(yīng)用層定義的buffer中,所以我們當(dāng)時(shí)就認(rèn)為IO其實(shí)就是數(shù)據(jù)拷貝。
但我們可以細(xì)微的想一想,只要你調(diào)用了recv,數(shù)據(jù)就一定能夠拷貝到應(yīng)用層buffer中嗎?會(huì)不會(huì)sk_buff中沒(méi)有數(shù)據(jù)呢?因?yàn)榭赡軟](méi)有客戶端向我的服務(wù)器發(fā)送數(shù)據(jù)。同理,只要你調(diào)用了send,數(shù)據(jù)就一定能夠拷貝到內(nèi)核sk_buff中嗎?會(huì)不會(huì)sk_buff中已經(jīng)堆滿了數(shù)據(jù),沒(méi)有剩余空間了呢?
如果這些情況都存在的話,recv,send這樣的接口會(huì)怎么做呢?答案是,這些接口一定會(huì)等!會(huì)等條件就緒的時(shí)候,再進(jìn)行數(shù)據(jù)拷貝,recv會(huì)等sk_buff中有數(shù)據(jù),send會(huì)等sk_buff中有剩余空間,等到條件就緒的時(shí)候,這些IO接口才會(huì)進(jìn)行數(shù)據(jù)拷貝。所以我們今天要重新定義IO,IO不僅僅是數(shù)據(jù)拷貝,同時(shí)還需要進(jìn)行等待,等待條件就緒時(shí),這些接口才會(huì)進(jìn)行數(shù)據(jù)拷貝,所以IO=等+數(shù)據(jù)拷貝

4.
其實(shí)我們是遇到過(guò)等這樣的情況的,以前在講進(jìn)程間通信的時(shí)候,管道通信時(shí),我們就遇到過(guò)等待的情況,例如寫(xiě)端不向管道中寫(xiě)數(shù)據(jù),此時(shí)讀端就會(huì)阻塞,阻塞的本質(zhì)其實(shí)就是在進(jìn)行等待,等待寫(xiě)端向管道中寫(xiě)數(shù)據(jù),所以讀取數(shù)據(jù)的IO接口在進(jìn)行等待的情況是比較常見(jiàn)的,但寫(xiě)數(shù)據(jù)時(shí)是不常見(jiàn)的,因?yàn)榇蠖鄶?shù)情況下,寫(xiě)事件是直接就緒的,因?yàn)閮?nèi)核發(fā)送緩沖區(qū)中常常是有剩余空間的,TCP有自己的滑動(dòng)窗口發(fā)送數(shù)據(jù)的策略,基本上寫(xiě)事件不就緒的情況很少見(jiàn),但讀事件不就緒還是很常見(jiàn)的,尤其是在網(wǎng)絡(luò)環(huán)境下進(jìn)行讀取數(shù)據(jù),因?yàn)閿?shù)據(jù)包在send調(diào)用后,數(shù)據(jù)包會(huì)被發(fā)送到內(nèi)核的sk_buff中,什么時(shí)候發(fā)送,怎么發(fā)送這些策略是由TCP來(lái)提供的,數(shù)據(jù)包在發(fā)送時(shí),會(huì)經(jīng)歷延遲應(yīng)答,查詢路由表確定下一跳路徑,在局域網(wǎng)中進(jìn)行轉(zhuǎn)發(fā)數(shù)據(jù)包,這些過(guò)程都是需要時(shí)間的,此時(shí)對(duì)端調(diào)用recv接收數(shù)據(jù)時(shí),在這些時(shí)間窗口內(nèi),recv不就得等嗎?

5.
而所謂高效的IO,其實(shí)就是降低等待的時(shí)間比重,因?yàn)閿?shù)據(jù)拷貝的時(shí)間比重基本上是確定的,他由硬件結(jié)構(gòu),操作系統(tǒng)優(yōu)化,編譯器優(yōu)化等條件所決定,或者可以提高帶寬,一次性拷貝較多的數(shù)據(jù),但這些的做法其實(shí)都是固定的,對(duì)數(shù)據(jù)拷貝的效率提升并不大,而影響IO效率最大的因素其實(shí)就是等待,只要IO模型等待的時(shí)間比重很低,那么這個(gè)IO我們就稱他是高效的。

2.有哪些IO模型?哪些模型是高效的?

1.
IO模型分為五種,分別是阻塞式IO,非阻塞IO,信號(hào)驅(qū)動(dòng)IO,多路轉(zhuǎn)接IO,異步IO。下面我們講一個(gè)例子先來(lái)淺淺談一下這5個(gè)模型IO的做法。
從前有一條小河,河里有許多條魚(yú),一個(gè)叫張三的少年就很喜歡釣魚(yú),他帶著自己的魚(yú)竿就去釣魚(yú)了,但張三這個(gè)人很固執(zhí),只要魚(yú)沒(méi)上鉤,張三就一直等著,什么都不干,死死的盯著魚(yú)漂,只有魚(yú)漂動(dòng)了,張三才會(huì)動(dòng),然后把魚(yú)釣上來(lái),釣上來(lái)之后,張三就又會(huì)重復(fù)之前的動(dòng)作,一動(dòng)不動(dòng)的等待魚(yú)兒上鉤。而此時(shí)走過(guò)來(lái)一個(gè)李四,李四這名少年也很喜歡釣魚(yú),但李四和張三不一樣,李四左口袋裝著《Linux高性能服務(wù)器編程》,右口袋裝著一本《算法導(dǎo)論》,左手拿手機(jī),右手拿了一根魚(yú)竿,李四拿了釣魚(yú)凳坐下之后,李四就開(kāi)始釣魚(yú)了,但李四不像張三一樣,固執(zhí)的死盯著魚(yú)漂看,李四一會(huì)看會(huì)兒左口袋的書(shū),一會(huì)玩會(huì)手機(jī),一會(huì)兒又看算法導(dǎo)論,一會(huì)又看魚(yú)漂,所以李四一直循環(huán)著前面的動(dòng)作,直到循環(huán)到看魚(yú)漂時(shí),發(fā)現(xiàn)魚(yú)漂已經(jīng)動(dòng)了好長(zhǎng)時(shí)間了,此時(shí)李四就會(huì)把魚(yú)兒釣上來(lái),之后繼續(xù)重復(fù)循環(huán)前面的動(dòng)作。此時(shí)又來(lái)了一個(gè)王五少年,王五就拿著他自己的iphone14pro max和一根魚(yú)竿外加一個(gè)鈴鐺,然后就來(lái)釣魚(yú)了,王五把鈴鐺掛到魚(yú)竿上,等魚(yú)上鉤的時(shí)候,鈴鐺就會(huì)響,王五根本不看魚(yú)竿,就一直玩自己的iphone,等魚(yú)上鉤的時(shí)候,鈴鐺會(huì)自動(dòng)響,王五此時(shí)再把魚(yú)兒釣上來(lái)就好了,之后王五又繼續(xù)重復(fù)前面的動(dòng)作,只要鈴鐺不響,王五就一直玩手機(jī),只有鈴鐺響了,王五才會(huì)把魚(yú)釣上來(lái)。此時(shí)又來(lái)了一個(gè)趙六的人,趙六和前面的三個(gè)人都不一樣,趙六是個(gè)小土豪,趙六手里拿了一堆魚(yú)竿,目測(cè)有幾百根魚(yú)竿,趙六到達(dá)河邊,首先就把幾百根魚(yú)竿每隔幾米插上去,總共插了好幾百米的魚(yú)竿,然后趙六就依次遍歷這些魚(yú)竿,哪個(gè)魚(yú)竿上的魚(yú)漂動(dòng)了,趙六就把這根魚(yú)竿上的魚(yú)釣上來(lái),然后接下來(lái)趙六就又繼續(xù)重復(fù)之前遍歷魚(yú)竿的動(dòng)作進(jìn)行釣魚(yú)了。然后又來(lái)了一個(gè)錢(qián)七,錢(qián)七比趙六還有錢(qián),錢(qián)七是上市公司的CEO,錢(qián)七有自己的司機(jī),錢(qián)七不喜歡釣魚(yú),但錢(qián)七喜歡吃魚(yú),所以錢(qián)七就把自己的司機(jī)留在了岸邊,并且給了司機(jī)一個(gè)電話和一個(gè)桶,告訴司機(jī),等你把魚(yú)釣滿一桶的時(shí)候,就給我打電話,然后我就從公司開(kāi)車(chē)過(guò)來(lái)接你,所以錢(qián)七就直接開(kāi)車(chē)回公司開(kāi)什么股東大會(huì)去了,而他的司機(jī)就被留在這里繼續(xù)釣魚(yú)了。

2.
在上面的例子中,你認(rèn)為誰(shuí)的釣魚(yú)方式更加高效呢?首先我們認(rèn)為,如果一個(gè)人在不停的釣魚(yú),時(shí)不時(shí)的就收魚(yú)竿,把魚(yú)釣上來(lái),等待魚(yú)兒上鉤的時(shí)間比重卻很低,那么這個(gè)人在我看來(lái)他的釣魚(yú)方式就是高效的。而如果一個(gè)人大部分的時(shí)間都是在等待,只有那么極少數(shù)次在收桿把魚(yú)釣上來(lái),那么這個(gè)人的釣魚(yú)方式就是低效的。
而上面的例子中,魚(yú)其實(shí)就是數(shù)據(jù),魚(yú)竿其實(shí)就是文件描述符fd,每個(gè)人都算是進(jìn)程,但除了錢(qián)七的司機(jī),這個(gè)司機(jī)算是操作系統(tǒng),河流就是內(nèi)核緩沖區(qū),魚(yú)漂就是就緒的事件,代表釣魚(yú)這件事情已經(jīng)就緒了,進(jìn)程可以對(duì)數(shù)據(jù)做拷貝了。
其實(shí)趙六的方式是最高效的,也就是多路轉(zhuǎn)接這種IO模型是最高效的,因?yàn)橼w六的魚(yú)竿多啊,釣上魚(yú)的幾率就大啊,其他人只有一根魚(yú)竿,只能關(guān)心這一根魚(yú)竿上的數(shù)據(jù),自然就沒(méi)有趙六的效率高,同理為什么渣男的女朋友多啊,因?yàn)閺V撒網(wǎng)嘛,找到女朋友的概率要比普通的老實(shí)人高啊,因?yàn)槿思乙淮慰梢躁P(guān)心那么多的微信賬號(hào),哪個(gè)女孩發(fā)消息了人家就和誰(shuí)聊天,肯定比你只有一個(gè)女孩的微信效率要高。
所以本文章主要來(lái)介紹多路轉(zhuǎn)接這種IO模型,同時(shí)也會(huì)講解阻塞和非阻塞IO,需要注意的是,實(shí)際項(xiàng)目中,最常用的就是阻塞IO,同時(shí)大部分的fd默認(rèn)就是阻塞的,因?yàn)檫@種IO太簡(jiǎn)單了,越簡(jiǎn)單的東西往往就越可靠,代碼編寫(xiě)也越簡(jiǎn)單,調(diào)試和找bug的難度也就越低,這樣的代碼可維護(hù)性很高,所以他就越常用。

3.五種IO模型的特性差別

1.
阻塞,非阻塞,信號(hào)驅(qū)動(dòng)在IO效率上是沒(méi)有差別的,因?yàn)樗麄內(nèi)齻€(gè)人都只有一根魚(yú)竿,等待魚(yú)上鉤的概率都是一樣的,相當(dāng)于他們等待事件就緒的概率是相同的,所以從IO效率上來(lái)看,這三個(gè)模型之間是沒(méi)有差別的。只不過(guò)三者等待的方式是不同的,阻塞是一直在進(jìn)行等待,而非阻塞可能會(huì)使用輪詢的方式來(lái)進(jìn)行等待,在等待的時(shí)間段內(nèi),非阻塞可能還會(huì)做一些其他的事情,信號(hào)驅(qū)動(dòng)和非阻塞一樣,在等待的時(shí)間段內(nèi),信號(hào)驅(qū)動(dòng)會(huì)做一些其他的事情,比如監(jiān)管一下其他的連接是否就緒等等事情。所以從IO的效率角度來(lái)講,這三種IO并無(wú)差別,因?yàn)镮O的過(guò)程分為等待和數(shù)據(jù)拷貝,三者在這個(gè)工作上的效率都是一樣的,只不過(guò)非阻塞和信號(hào)驅(qū)動(dòng)的等待方式與阻塞IO不同。信號(hào)驅(qū)動(dòng)只不過(guò)是被動(dòng)的等待,阻塞和非阻塞都是主動(dòng)的等待,當(dāng)信號(hào)到來(lái)時(shí),信號(hào)驅(qū)動(dòng)IO會(huì)通過(guò)回調(diào)的方式來(lái)處理就緒的事件。

2.
而多路轉(zhuǎn)接相比前三種IO模型更為高效一些,因?yàn)樗軌蛞淮蔚却鄠€(gè)文件描述符,但這四種IO都有一個(gè)共同的特征,就是直接參與了IO的過(guò)程,這樣的通信我們稱之為同步通信,而異步IO是典型的異步通信,他將等待數(shù)據(jù)就緒的事情交給了內(nèi)核來(lái)處理,當(dāng)數(shù)據(jù)準(zhǔn)備好后,操作系統(tǒng)會(huì)以信號(hào)或回調(diào)函數(shù)的方式來(lái)通知進(jìn)程可以處理數(shù)據(jù)了,因?yàn)閿?shù)據(jù)已經(jīng)準(zhǔn)備好了,這就是典型的異步通信。

3.
所以以后在看到同步這個(gè)詞時(shí),一定要確定好他的大背景是什么,是同步通信還是異步通信,這指的是進(jìn)程的消息通信機(jī)制不同,前者是同步IO的方式,也就是主動(dòng)等待調(diào)用返回的結(jié)果,后者是異步IO的方式,調(diào)用是直接返回的,后續(xù)執(zhí)行調(diào)用的操作系統(tǒng)會(huì)以某種方式來(lái)通知進(jìn)程。
或者同步也有可能是線程的同步與互斥,線程同步指的是多個(gè)線程之間通過(guò)條件變量的方式來(lái)互相協(xié)同工作,完成某一件任務(wù)。

二、阻塞與非阻塞IO

1.
每一個(gè)打開(kāi)的文件所對(duì)應(yīng)的文件描述符fd,默認(rèn)全都是阻塞的,無(wú)論是系統(tǒng)文件fd,還是網(wǎng)絡(luò)套接字sock,都默認(rèn)是阻塞的IO方式。
設(shè)置fd為非阻塞的方式大概有三種,在open打開(kāi)文件的時(shí)候,可以攜帶選項(xiàng)O_NONBLOCK,或者在使用網(wǎng)絡(luò)IO接口,例如send,recv等時(shí),可以攜帶額外的選項(xiàng)MSG_DONTWAIT,另一種是最常用的方式,就是fcntl系統(tǒng)調(diào)用,像open那樣的方式只適合打開(kāi)系統(tǒng)文件,對(duì)于網(wǎng)絡(luò)套接字就不太適合,況且打開(kāi)文件時(shí),還要記住這么多的額外選項(xiàng),對(duì)于程序員還是不太友好,像fcntl這樣的方式,無(wú)論對(duì)于系統(tǒng)文件還是網(wǎng)絡(luò)套接字都是完全適用的。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

2.
fcntl函數(shù)有5種功能,我們這里只使用第三種功能,也就是設(shè)置文件描述符的狀態(tài)標(biāo)記,先通過(guò)F_GETFL選項(xiàng)獲取原有文件描述符的標(biāo)志位,然后再通過(guò)F_SETFL選項(xiàng)將原有的標(biāo)志位與O_NONBLOCK按位或之后,再重新設(shè)置回文件中。這樣就可以將文件描述符設(shè)置為非阻塞了。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

3.
下面的運(yùn)行結(jié)果就是典型的阻塞式IO,當(dāng)程序運(yùn)行起來(lái)時(shí),執(zhí)行流會(huì)在read處阻塞,因?yàn)閞ead今天讀取的是0號(hào)文件描述符,也就是鍵盤(pán)文件上的數(shù)據(jù),只要我不從鍵盤(pán)上輸入數(shù)據(jù)的話,read就會(huì)一直阻塞,此時(shí)進(jìn)程會(huì)被操作系統(tǒng)掛起,直到硬件設(shè)備鍵盤(pán)上有數(shù)據(jù)時(shí),進(jìn)程才會(huì)重新投入CPU的運(yùn)行隊(duì)列,當(dāng)我們輸入數(shù)據(jù)后,可以立馬看到進(jìn)程顯示出了echo回應(yīng)的結(jié)果,同時(shí)進(jìn)程又立馬陷入阻塞,等待我進(jìn)行下一次的輸入數(shù)據(jù),這樣的IO方式就是典型的阻塞式,同時(shí)也是最常用,最簡(jiǎn)單的IO方式。
在這里額外補(bǔ)充一下,linux命令行中表示輸入結(jié)束的快捷鍵是ctrl+d,當(dāng)此熱鍵被用戶按下后,代表0號(hào)文件描述符寫(xiě)端關(guān)閉,此時(shí)讀端會(huì)讀到0,read會(huì)返回0值,此時(shí)進(jìn)程除了輸出提示信息"read file end"外,還應(yīng)該加一個(gè)break,跳出循環(huán),結(jié)束進(jìn)程。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

4.
下面就是非阻塞IO的實(shí)驗(yàn)結(jié)果,非阻塞IO在沒(méi)有讀到數(shù)據(jù)時(shí),并不會(huì)卡在read系統(tǒng)調(diào)用處不動(dòng),等待0號(hào)fd到來(lái)數(shù)據(jù),而是read立馬返回,同時(shí)read的返回值為-1,所以你可以看到,while循環(huán)里面在不停的打印>>>輸出信息,因?yàn)閞ead在未讀取到數(shù)據(jù)時(shí),會(huì)立馬返回,并不會(huì)阻塞住。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器
下面有設(shè)置fd為非阻塞IO的接口SetNonBlock(),設(shè)置的方式也很簡(jiǎn)單,只需要先獲得fd原有的標(biāo)志位fl,然后再將fl與O_NONBLOCK按位或,重新設(shè)置到文件描述符fd即可,是不是很簡(jiǎn)單呢?
【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器
5.
非阻塞IO時(shí),read的返回結(jié)果是-1,這樣合理嗎?底層沒(méi)有數(shù)據(jù),這算錯(cuò)誤嗎?其實(shí)這并不算錯(cuò)誤,只不過(guò)當(dāng)?shù)讓記](méi)有數(shù)據(jù)時(shí),read以錯(cuò)誤的方式返回了,但我們?cè)撊绾螀^(qū)分read接口是真的調(diào)用失敗了(比如read讀取了一個(gè)不存在的fd),還是僅僅底層沒(méi)有數(shù)據(jù)罷了,當(dāng)然通過(guò)read的返回值我們是無(wú)法區(qū)分的,因?yàn)閞ead在這兩種情況下都返回-1,但可以通過(guò)錯(cuò)誤碼來(lái)區(qū)分,當(dāng)非阻塞IO返回時(shí),如果是底層沒(méi)有數(shù)據(jù),錯(cuò)誤碼會(huì)是EWOULDBLOCK或EAGAIN,如果read是真的出錯(cuò)調(diào)用了,會(huì)有相對(duì)應(yīng)的錯(cuò)誤碼。
同時(shí)在非阻塞等待期間,進(jìn)程也可以做一些其他的任務(wù),例如打印一些日志,下載某些文件,執(zhí)行某些SQL語(yǔ)句等等,執(zhí)行的方式也很簡(jiǎn)單,先將這些函數(shù)加載到一個(gè)vector里,然后在非阻塞IO的循環(huán)內(nèi)部執(zhí)行一個(gè)宏函數(shù),宏函數(shù)內(nèi)部其實(shí)就是遍歷vector容器,依次執(zhí)行容器中的函數(shù)指針?lè)椒ā?br> 需要額外解釋的一個(gè)錯(cuò)誤碼就是EINTR,代表interrupted,也就是系統(tǒng)調(diào)用被中斷,有可能read在系統(tǒng)調(diào)用執(zhí)行的過(guò)程中,內(nèi)核會(huì)檢查進(jìn)程關(guān)于信號(hào)的三張表block表,pending表,handler表,而檢查之前,恰好進(jìn)程收到了來(lái)自操作系統(tǒng)的信號(hào),所以此時(shí)有可能進(jìn)程運(yùn)行級(jí)別會(huì)重新變?yōu)橛脩魬B(tài),轉(zhuǎn)而執(zhí)行用戶定義的handler方法,也就是信號(hào)對(duì)應(yīng)的處理函數(shù),在執(zhí)行完handler方法返回時(shí),read系統(tǒng)調(diào)用會(huì)被中斷,read返回-1,同時(shí)錯(cuò)誤碼被設(shè)置為EINTR。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器
【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

三、select_server

1.select系統(tǒng)調(diào)用詳解

1.
select是我們學(xué)習(xí)的第一個(gè)多路轉(zhuǎn)接IO接口,select只負(fù)責(zé)IO過(guò)程中等待的這一步,也就是說(shuō),用戶可能關(guān)心一些sock上的讀事件,想要從sock中讀取數(shù)據(jù),直接讀取,可能recv調(diào)用會(huì)阻塞,等待數(shù)據(jù)到來(lái),而此時(shí)服務(wù)器進(jìn)程就會(huì)被阻塞掛起,但服務(wù)器掛起就完蛋了,服務(wù)器就無(wú)法給客戶提供服務(wù),可能會(huì)產(chǎn)生很多無(wú)法預(yù)料的不好影響,萬(wàn)一客戶正轉(zhuǎn)賬呢,服務(wù)器突然掛起了,客戶的錢(qián)沒(méi)了,但商家這里又沒(méi)有收到錢(qián),客戶找誰(shuí)說(shuō)理去啊,所以服務(wù)器掛起是一個(gè)問(wèn)題,我們要避免產(chǎn)生這樣的問(wèn)題。
而select的作用就是幫用戶關(guān)心sock上的讀事件,等sock中有數(shù)據(jù)時(shí),select此時(shí)會(huì)返回,告知用戶你所關(guān)心的sock上的讀事件已經(jīng)就緒了,用戶你可以調(diào)用recv讀取sock中的數(shù)據(jù)了!所以多路轉(zhuǎn)接其實(shí)是把IO的過(guò)程分開(kāi)來(lái)執(zhí)行了,用多路復(fù)用接口來(lái)監(jiān)視fd上的事件是否就緒,一旦就緒就會(huì)立馬通知上層,讓上層調(diào)用對(duì)應(yīng)的接口進(jìn)行數(shù)據(jù)的處理,等待和數(shù)據(jù)拷貝的工作分開(kāi)執(zhí)行,這樣的IO效率一定是高的,因?yàn)橄駍elect這樣的多路轉(zhuǎn)接接口,一次能夠等待多個(gè)fd,在返回時(shí),它可以把多個(gè)fd中所有就緒的fd全部返回并通知給上層。

2.
當(dāng)程序運(yùn)行時(shí),程序其實(shí)會(huì)在select這里進(jìn)行等待,遍歷一次底層的多個(gè)fd,看其中哪個(gè)fd就緒了,然后就將就緒的fd返回給上層,select的第一個(gè)參數(shù)nfds代表監(jiān)視的fd中最大的fd值+1,其實(shí)就是select在底層需要遍歷所有監(jiān)視的fd,而這個(gè)nfds參數(shù)其實(shí)就是告知select底層遍歷的范圍是多大,后四個(gè)參數(shù)全部是輸入輸出型參數(shù),兼具用戶告訴內(nèi)核 和 內(nèi)核告訴用戶消息的作用,比如timeout參數(shù),輸入時(shí),代表用戶告知內(nèi)核select監(jiān)視等待fd時(shí)的方式,nullptr代表select阻塞等待fd就緒,當(dāng)有fd就緒時(shí),select才會(huì)返回,傳0代表非阻塞等待fd就緒,即select只會(huì)遍歷檢測(cè)一遍底層的fd,不管有沒(méi)有fd就緒,select都會(huì)返回,傳大于0的值,代表在該時(shí)間范圍內(nèi)select阻塞等待,超出該時(shí)間select直接非阻塞返回。假設(shè)你輸入的timeout參數(shù)值為5s,如果在第3時(shí)select檢測(cè)到有fd就緒并且返回時(shí),內(nèi)核會(huì)在select調(diào)用內(nèi)部將timeout的值修改為2s,這就是輸出型參數(shù)的作用,內(nèi)核告知用戶,timeout值為2s,select等待的時(shí)間為3s。
select返回值有三種含義,大于0代表就緒的文件描述符個(gè)數(shù),等于0代表select超時(shí)返回,小于0代表select真的是出錯(cuò)返回了,比如你讓select監(jiān)視一個(gè)根本就不存在的fd,那此時(shí)select就會(huì)出錯(cuò)返回-1

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

3.
select中間的三個(gè)參數(shù)也是輸入輸出型參數(shù),作為用戶與內(nèi)核交流的橋梁,下面是fd_set結(jié)構(gòu)體類型的源碼,其實(shí)fd_set結(jié)構(gòu)體也好理解,他其實(shí)就是一個(gè)位圖結(jié)構(gòu),而所謂的位圖結(jié)構(gòu)就是用結(jié)構(gòu)體包數(shù)組的方式來(lái)實(shí)現(xiàn)的,__fd_mask其實(shí)就是8字節(jié)的long int類型,__FD_SETSIZE/__NFDBITS的大小是16,所以這個(gè)位圖我們可以直接看成大小為16的long int類型的數(shù)組,共有16×8×8個(gè)bit位,所以select最大也就支持監(jiān)視1024個(gè)fd,這其實(shí)也是select的缺點(diǎn)之一,后面我們會(huì)總結(jié)select的缺點(diǎn)。
fd_set確實(shí)就是一個(gè)位圖,但這個(gè)位圖結(jié)構(gòu)我們不應(yīng)該自己去手動(dòng)向其添加,刪除或修改fd,而應(yīng)該使用內(nèi)核為我們提供的相應(yīng)的位操作的接口來(lái)進(jìn)行操作。用戶可以通過(guò)向這些位操作的接口來(lái)告知內(nèi)核,用戶要關(guān)心哪些fd上的什么事件,select中間三個(gè)參數(shù)分別代表了用戶要告知內(nèi)核的信息,readfds是用戶告知內(nèi)核要關(guān)心fd上的讀事件,writefds是用戶告知內(nèi)核要關(guān)心fd上的寫(xiě)事件,exceptfds是用戶告知內(nèi)核要關(guān)心fd上的異常事件,而當(dāng)select調(diào)用返回時(shí),fd_set又會(huì)作為輸出型參數(shù),內(nèi)核告知用戶你所關(guān)心的fd中,已經(jīng)就緒的fd我給你放到fd_set結(jié)構(gòu)體里面了,所以在select調(diào)用結(jié)束后,用戶定義的fd_set結(jié)構(gòu)體中的內(nèi)容會(huì)被內(nèi)核修改,從用戶定義的需要內(nèi)核關(guān)心的fd 修改為 就緒的fd事件。這就是輸入輸出型參數(shù)的作用,作為橋梁來(lái)達(dá)到用戶與內(nèi)核進(jìn)行互相交流的目的。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

讓我們來(lái)和歷史碰個(gè)面,當(dāng)時(shí)在學(xué)linux信號(hào)的時(shí)候,我們所說(shuō)的三張表中的blocked表其實(shí)就是位圖結(jié)構(gòu),當(dāng)時(shí)我們也是用操作系統(tǒng)提供的接口來(lái)對(duì)blocked表進(jìn)行操作的,所以這里的fd_set位圖與當(dāng)時(shí)我們所學(xué)的信號(hào)集是恰恰相似的。
【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

2.select服務(wù)器代碼編寫(xiě)

1.
服務(wù)器暴露給外面的接口是initServer( )和start( ),用于初始化服務(wù)器和啟動(dòng)服務(wù)器,在start中,服務(wù)器就要開(kāi)始accept拿取完成三次握手的全連接了,但服務(wù)器能直接accept拿取連接嗎?服務(wù)器怎么確定當(dāng)前內(nèi)核監(jiān)聽(tīng)隊(duì)列中一定有連接就緒呢?只有select才能監(jiān)視listensock上的讀事件是否就緒,所以我們要先把listensock上的讀事件添加到fd_set位圖結(jié)構(gòu)中,讓內(nèi)核幫我們關(guān)心listensock上的讀事件,等listensock上的讀事件就緒之后,服務(wù)器再調(diào)用accept拿取連接,此時(shí)accept就不會(huì)阻塞等待,而是直接拿取就緒好的連接。
上面的工作我們可以根據(jù)select的返回值來(lái)進(jìn)行,當(dāng)返回值大于0時(shí),直接調(diào)用HandlerReadEvent接口處理rfds位圖中就緒的讀事件

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器
2.
在HandlerReadEvent中,要處理的讀事件可能有兩種情況,一種是listensock上的讀事件,一種是普通sock上的讀事件,所以要通過(guò)rfds位圖里面的有效的bit位來(lái)判斷是哪一種讀事件,但其實(shí)這里就會(huì)有一個(gè)問(wèn)題產(chǎn)生,既然fd_set中可能有很多的fd存在,那服務(wù)器要處理的事件就會(huì)很多,accept拿取連接之后,得到一個(gè)通信的sock,這個(gè)通信的sock能直接進(jìn)行recv讀取數(shù)據(jù)嗎?當(dāng)然也不能,這個(gè)sock必須也要交給select監(jiān)視,等到sock就緒的時(shí)候,才能夠recv讀取數(shù)據(jù),但HandlerReadEvent如何告知select,你不僅要幫我關(guān)心listensock,還要幫我關(guān)心通信使用的sock呢?
總不能通過(guò)輸出型參數(shù)來(lái)添加到rfds位圖吧,select最惡心的地方是,內(nèi)核會(huì)修改rfds位圖,如果你要通過(guò)輸出型參數(shù)的方式來(lái)將關(guān)心的fd添加到rfds中,則每次select調(diào)用返回后,你需要記錄下來(lái)你所關(guān)心的所有fd,然后在下一次select調(diào)用之前,將這些所有的關(guān)心的fd再重新放到rfds里面,重點(diǎn)是你需要記錄下來(lái)所有的fd,像listensock作為服務(wù)器的私有成員變量,這個(gè)記錄下來(lái)并不麻煩,但如果后期accept100多個(gè)連接呢?難道定義100多個(gè)sock,把所有sock記錄下來(lái),然后再逐個(gè)添加到fd_set中嗎?這樣未免也太麻煩了吧!
所以在select這里,需要借助一個(gè)第三方數(shù)組fd_array來(lái)存儲(chǔ)用戶需要關(guān)心的fd,在每次調(diào)用select前將這個(gè)數(shù)組中合法的fd全部添加到fd_set中,然后讓select幫我們?nèi)リP(guān)心。我們添加第三方數(shù)組來(lái)存儲(chǔ)fd,主要還是因?yàn)閟elect接口中位圖參數(shù)rfds是輸入輸出型參數(shù),每次select接口返回時(shí),內(nèi)核都會(huì)修改rfds中的值,所以這就導(dǎo)致下一次調(diào)用select前,我們需要重新設(shè)置rfds位圖中關(guān)心的fd,這也是select接口的缺點(diǎn)之一。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

3.
下面是服務(wù)器處理listensock上的讀事件時(shí),Accepter接口的實(shí)現(xiàn),今天我們的服務(wù)器就不處理寫(xiě)事件了,等后面寫(xiě)Reactor網(wǎng)絡(luò)庫(kù)的時(shí)候,到時(shí)候我們把所有的事件都處理一遍,今天我們就只處理讀事件。
在accept拿取到通信的sock后,我們要把通信的sock添加到fd_array中,在下一次調(diào)用select的時(shí)候,讓內(nèi)核幫我們關(guān)心sock上的讀事件。添加的時(shí)候,這里其實(shí)也有坑,由于fd_set位圖最大只能支持1024個(gè)bit位,所以select能夠同時(shí)監(jiān)視的fd是有上限的,在添加sock到fd_array的循環(huán)內(nèi)部,我們要找出空余的bit位,然后將sock添加到這個(gè)bit位里面,此時(shí)跳出循環(huán)就有兩種情況,一種是fd_set真的滿了,另一種是fd_set有空余的位置。
添加完成之后,當(dāng)執(zhí)行流重新回到HandlerReadEvent里面,直到HandlerReadEvent結(jié)束后回到start時(shí),在執(zhí)行select前,會(huì)將fd_array中合法的fd全部添加到rfds位圖中。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器
4.
下面是處理通信sock時(shí)的接口Recver,進(jìn)入Recver后sock確實(shí)就緒了,但直接上來(lái)就recv讀取數(shù)據(jù),其實(shí)還是有問(wèn)題的,最典型的問(wèn)題就是黏包問(wèn)題,你怎么保證你一次就能夠讀取上來(lái)一個(gè)完整的報(bào)文呢?如果你循環(huán)讀取,又如何保證后面調(diào)用的recv不會(huì)阻塞呢?其實(shí)這個(gè)不用保證,這個(gè)問(wèn)題算是比較多慮的,因?yàn)橹灰M(jìn)入了Recver接口,這就能夠保證sock底層是有數(shù)據(jù)的,如果一次不能讀到一個(gè)完整的報(bào)文,那就可以再讀第二次,第三次……,直到讀取上來(lái)的數(shù)據(jù)能夠解析出一個(gè)完整的報(bào)文,讀取出一個(gè)完整的報(bào)文后,我們要對(duì)報(bào)文進(jìn)行反序列化,將字節(jié)流式的數(shù)據(jù)進(jìn)行解析,得到結(jié)構(gòu)化的數(shù)據(jù),當(dāng)然這些就是應(yīng)用層的處理工作了,我們不詳談。
當(dāng)recv讀到0,說(shuō)明寫(xiě)端把sock關(guān)閉了,則服務(wù)器也應(yīng)該關(guān)閉套接字,同時(shí)將fd_array中的套接字置為無(wú)效,也就是置為-1,今天我們服務(wù)器的應(yīng)用層處理工作非常簡(jiǎn)單,其實(shí)就是將客戶端發(fā)送過(guò)來(lái)的消息反回去即可,func是main中傳到select_server類的一個(gè)回調(diào)方法,用于業(yè)務(wù)邏輯處理客戶端發(fā)送過(guò)來(lái)的信息,處理完之后,直接調(diào)用send將響應(yīng)發(fā)回給客戶端,同樣這里也是有問(wèn)題的,你怎么保證send一定能夠發(fā)送數(shù)據(jù)呢?sock的寫(xiě)事件就緒,你send是不知道的啊,但今天我們先不管,因?yàn)榇蟾怕蕦?xiě)事件是直接就緒的,因?yàn)榉?wù)器前面又沒(méi)有發(fā)送過(guò)什么東西,發(fā)送緩沖區(qū)大概率是有空間的。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

5.
下面是select_服務(wù)器的完整代碼,其實(shí)想要實(shí)現(xiàn)這個(gè)服務(wù)器還是很簡(jiǎn)單的,需要注意的點(diǎn)就是select得借助第三方數(shù)組fd_array來(lái)保存用戶關(guān)心的fd,每一次調(diào)用select之前都需要重新將fd_array中用戶關(guān)心的fd設(shè)置到select的參數(shù)中。
其他一些還需要注意的點(diǎn)就是處理讀事件時(shí),listensock和通信的sock他們之間的處理邏輯是不同的,應(yīng)該由不同的模塊去完成他們的邏輯,就比如代碼中的Accepter和Recver將兩個(gè)sock的事件分開(kāi)處理。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

下面是封裝的一些socket編程接口,封裝后用起來(lái)更簡(jiǎn)潔一些,代碼可讀性更好

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

3.select服務(wù)器的缺點(diǎn)

1.
select并不是多路轉(zhuǎn)接中好的一個(gè)方案,當(dāng)然這并不代表他是有問(wèn)題的,只不過(guò)他用起來(lái)成本較高,要關(guān)注的點(diǎn)也比較多,所以我們說(shuō)他并不是一個(gè)好的方案。

2.
同時(shí)select也有缺點(diǎn),比如select監(jiān)視的fd是有上限的,我的云服務(wù)器內(nèi)核版本下最大上限是1024個(gè)fd,主要還是因?yàn)閒d_set他是一個(gè)固定大小的位圖結(jié)構(gòu),位圖中的數(shù)組開(kāi)辟之后不會(huì)在變化了,這是內(nèi)核的數(shù)據(jù)結(jié)構(gòu),除非你修改內(nèi)核參數(shù),否則不會(huì)在變化了,所以一旦select監(jiān)視的fd數(shù)量超過(guò)1024,則select會(huì)報(bào)錯(cuò)。
除此之外,select大部分的參數(shù)都是輸入輸出型參數(shù),用戶和內(nèi)核都會(huì)不斷的修改這些參數(shù)的值,導(dǎo)致每次調(diào)用select前,都需要重新設(shè)置fd_set位圖中的內(nèi)容,這在用戶層面上會(huì)帶來(lái)很多不必要的遍歷+拷貝的成本。
同時(shí)select還需要借助第三方數(shù)組來(lái)維護(hù)用戶需要關(guān)心的fd,這也是select使用不方便的一種體現(xiàn)。而上面的這些問(wèn)題,正是其他多路轉(zhuǎn)接接口所存在的意義,poll解決了很多select接口存在的問(wèn)題。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

四、poll_server

1.poll系統(tǒng)調(diào)用詳解

1.
poll接口主要解決了select接口的兩個(gè)問(wèn)題,一個(gè)是select監(jiān)視的fd有上限,另一個(gè)是select每次調(diào)用前都需要借助第三方數(shù)組,向fd_set里面重新設(shè)置關(guān)心的fd。
poll的第一個(gè)參數(shù)是結(jié)構(gòu)體數(shù)組的地址,數(shù)組中的每個(gè)元素都是struct pollfd結(jié)構(gòu)體,該結(jié)構(gòu)體內(nèi)部包含三個(gè)字段,fd表示用戶告知內(nèi)核需要關(guān)心的fd,events表示用戶告知內(nèi)核需要關(guān)心fd上面的什么事件,revents表示內(nèi)核告知用戶,你所關(guān)心的fd上面的revents已經(jīng)就緒了,poll的第二個(gè)參數(shù)是nfds,表示該結(jié)構(gòu)體數(shù)組的大小,nfds_t其實(shí)是unsigned long int類型的重定義,在64位系統(tǒng)下是8字節(jié)的大小,所以nfds這個(gè)數(shù)的最大值為2^64次方大小,也就是42億×42億,最大是18446744073709600000,在計(jì)算機(jī)中不可能存在這么多的文件描述符,所以雖然在數(shù)學(xué)意義上結(jié)構(gòu)體數(shù)組fds也是有上限的,但在計(jì)算機(jī)中,這個(gè)上限是沒(méi)有任何意義的,因?yàn)椴豢赡艽嬖谶@么多的文件描述符,所以我們就認(rèn)為poll解決了select監(jiān)視的fd有上限的問(wèn)題。
同時(shí)結(jié)構(gòu)體中fd和events字段是輸入型參數(shù),revents是輸出型參數(shù),用戶告知內(nèi)核要關(guān)心的fd,內(nèi)核告知用戶已經(jīng)就緒的fd,在poll中是解耦開(kāi)來(lái)的,不像在select中這兩件事都是通過(guò)輸入輸出型參數(shù)來(lái)完成的,耦合在了一起,所以poll是不需要借助第三方數(shù)組的,直接向結(jié)構(gòu)體數(shù)組中添加結(jié)構(gòu)體即可,無(wú)須每次在調(diào)用poll前進(jìn)行重新設(shè)置,因?yàn)閜oll對(duì)輸入和輸出進(jìn)行了解耦分離。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

2.
下面是events和revents的取值,最常用的就是POLLIN和POLLOUT,分別代表關(guān)心fd上的讀事件和寫(xiě)事件,POLLRDBAND表示關(guān)心fd上的帶外數(shù)據(jù)。這些大寫(xiě)的值其實(shí)都是宏,這些宏會(huì)賦值給2字節(jié)short類型的events和revents

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

3.
poll的返回值含義與select相同,大于0表示就緒的fd個(gè)數(shù),等于0代表超時(shí)返回,小于0代表出錯(cuò)返回,timeout代表poll監(jiān)視fd時(shí)的策略,大于0代表該數(shù)值范圍內(nèi)阻塞式監(jiān)視,超過(guò)該數(shù)值則直接非阻塞返回,小于0代表一直阻塞式監(jiān)視,直到某些fd就緒,等于0代表非阻塞監(jiān)視,poll會(huì)遍歷一遍用戶關(guān)心的fd,無(wú)論是否有fd就緒,poll都會(huì)直接返回。poll接口的timeout參數(shù)和select接口不太一樣,select接口的timeout參數(shù)是輸入輸出型參數(shù),而poll接口的timeout參數(shù)是純輸入型參數(shù),只有用戶會(huì)對(duì)timeout做出修改,內(nèi)核并不會(huì)。

2.poll服務(wù)器代碼編寫(xiě)

1.
下面將剛剛的select服務(wù)器代碼用poll接口來(lái)改寫(xiě)實(shí)現(xiàn)一下。
poll服務(wù)器和剛剛的select服務(wù)器非常的相似,只不過(guò)剛剛的select成員變量有一個(gè)fd_array用于存儲(chǔ)用戶關(guān)心的fd,而現(xiàn)在的poll服務(wù)器用了一個(gè)_rfds變量,用于存儲(chǔ)結(jié)構(gòu)體數(shù)組的起始地址,這個(gè)結(jié)構(gòu)體指針就是傳給poll接口的第一個(gè)參數(shù)。
pollServer.hpp主要的接口和select一樣,只是把接口里面select部分替換成了poll接口的使用,在初始化服務(wù)器時(shí),需要開(kāi)辟一個(gè)結(jié)構(gòu)體數(shù)組,這個(gè)數(shù)組開(kāi)辟在堆上,這個(gè)數(shù)組其實(shí)比較標(biāo)準(zhǔn)的寫(xiě)法是搞成擴(kuò)容版本的,也就是vector,但今天為了簡(jiǎn)便,我們就搞成固定大小的數(shù)組。開(kāi)辟完數(shù)組之后,先將數(shù)組的每個(gè)結(jié)構(gòu)體的內(nèi)容做一下重新設(shè)置,也就是初始化,將fd設(shè)置為-1,events和revents設(shè)置為0,代表無(wú)事件。
初始化完數(shù)組之后,我們可以直接添加listensock的讀事件到結(jié)構(gòu)體數(shù)組的第一個(gè)位置上,因?yàn)榉?wù)器首先需要關(guān)心的一定是listensock的讀事件,所以我們就可以這么做,至此服務(wù)器的初始化工作就順利完成。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器
2.
下面是服務(wù)器的運(yùn)行邏輯,這個(gè)運(yùn)行邏輯寫(xiě)起來(lái)可比select簡(jiǎn)單多了,因?yàn)閜oll接口不需要在每次調(diào)用前重新設(shè)置fd到結(jié)構(gòu)體中,所以寫(xiě)起來(lái)非常的簡(jiǎn)潔,當(dāng)poll的返回值大于0時(shí),說(shuō)明有事件就緒了,那我們就執(zhí)行HandlerReadEvent( )接口。
在HandlerReadEvent( )中,需要遍歷整個(gè)結(jié)構(gòu)體數(shù)組,而結(jié)構(gòu)體數(shù)組的大小在初始化時(shí)就確定了,也就是num大小,num是一個(gè)全局靜態(tài)的常量大小我初始設(shè)置為2048,所以HandlerReadEvent中要遍歷整個(gè)結(jié)構(gòu)體數(shù)組,看哪個(gè)結(jié)構(gòu)體中的revents值已經(jīng)被內(nèi)核設(shè)置了,如果被設(shè)置,那就說(shuō)明該結(jié)構(gòu)體是就緒的,HandlerReadEvent就應(yīng)該處理這個(gè)結(jié)構(gòu)體上已經(jīng)就緒的事件。今天pollServer和selectServer一樣,都只處理讀事件,所以我們可以在for循環(huán)內(nèi)部先判斷fd是否合法,然后在判斷events是否被設(shè)置為POLLIN,如果沒(méi)有被設(shè)置,說(shuō)明這個(gè)fd并不需要被處理,再走到下面的分支語(yǔ)句,其實(shí)就是兩種情況的判斷了,一種是listensock上的讀事件處理,另一種就是通信sock上的讀事件處理,這兩種事件和selectServer一樣,分別交給Accepter和Recver來(lái)處理。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

3.
Accepter中可以直接調(diào)用accept系統(tǒng)調(diào)用,獲取上來(lái)用于IO的sock,因?yàn)榇藭r(shí)listensock上的讀事件一定是就緒的,在獲得sock之后,下一步要做的工作就是把sock交給poll接口進(jìn)行監(jiān)視,監(jiān)視sock上面的讀事件,而托管給poll監(jiān)視的本質(zhì),其實(shí)就是將sock放到結(jié)構(gòu)體數(shù)組_rfds中,所以只需要遍歷_rfds,找出空閑的結(jié)構(gòu)體位置,然后將sock和POLLIN事件設(shè)置到結(jié)構(gòu)體中即可,跳出循環(huán)同樣也有兩種情況,一種是結(jié)構(gòu)體數(shù)組沒(méi)有空余的位置了,但其實(shí)這種情況一般不會(huì)存在,只要將數(shù)組設(shè)置為柔性數(shù)組可擴(kuò)容即可,另一種情況就非常的簡(jiǎn)單了,填充struct pollfd的三個(gè)字段值就可以了,這樣就完成了服務(wù)器的IO代碼模塊兒。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器
4.
pollserver的調(diào)用邏輯和select一模一樣,沒(méi)什么好說(shuō)的,代碼很好理解,就是用智能指針來(lái)將服務(wù)器對(duì)象的生命周期和指針的生命周期進(jìn)行綁定,用RAII的方式來(lái)防止可能產(chǎn)生的內(nèi)存泄露,只要智能指針?shù)N毀,則服務(wù)器對(duì)象資源也會(huì)跟著被銷毀。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

下面是完整的代碼
【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

3.poll所存在的缺點(diǎn)

1.
其實(shí)poll的優(yōu)點(diǎn)就是解決了select支持的fd有上限,以及用戶輸入信息和內(nèi)核輸出信息耦合的兩個(gè)問(wèn)題。
但poll的缺點(diǎn)其實(shí)在上面的代碼已經(jīng)體現(xiàn)出來(lái)了一部分,內(nèi)核在檢測(cè)fd是否就緒時(shí),需要遍歷整個(gè)結(jié)構(gòu)體數(shù)組檢測(cè)events的值,同樣用戶在處理就緒的fd事件時(shí),也需要遍歷整個(gè)結(jié)構(gòu)體數(shù)組檢測(cè)revents的值,當(dāng)rfds結(jié)構(gòu)體數(shù)組越來(lái)越大時(shí),每次遍歷數(shù)組其實(shí)就會(huì)降低服務(wù)器的效率,為此,內(nèi)核提供了epoll接口來(lái)解決這樣的問(wèn)題。
與select相同的是,poll也需要用戶自己維護(hù)一個(gè)第三方數(shù)組來(lái)存儲(chǔ)用戶需要關(guān)心的fd及事件,只不過(guò)poll不需要在每次調(diào)用前都重新設(shè)置關(guān)心的fd,因?yàn)橛脩舻妮斎牒蛢?nèi)核的輸出是分離的,分別在結(jié)構(gòu)體的events和revents的兩個(gè)字段,做到了輸入和輸出分離。

五、epoll_server

1.epoll系統(tǒng)調(diào)用詳解

1.
epoll是公認(rèn)的最高效的多路轉(zhuǎn)接接口,man手冊(cè)中描述epoll是為了處理大量的句柄而作了改進(jìn)的poll,也就是extendPoll擴(kuò)展性的poll,我們上面說(shuō)到過(guò),當(dāng)大量的句柄到來(lái)時(shí),poll會(huì)由于頻繁的遍歷所有的句柄而導(dǎo)致效率降低,epoll的出現(xiàn)就解決了這樣的問(wèn)題。
雖然說(shuō)epoll是作了改進(jìn)的poll,但在接口的使用和底層實(shí)現(xiàn)上,epoll和poll天差地別,在linux內(nèi)核2.5.44版本時(shí),就引入了epoll接口,而現(xiàn)在主流的linux內(nèi)核版本已經(jīng)是3點(diǎn)幾了。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器
2.
epoll_create會(huì)在內(nèi)核幫我們創(chuàng)建一個(gè)epoll模型,這個(gè)epoll模型非常的重要,可以幫助我們理解epoll高效的原因,以及他工作的機(jī)制,所謂的epoll模型其實(shí)也是一個(gè)struct file結(jié)構(gòu)體,所以epoll_create創(chuàng)建epoll模型成功后,會(huì)返回一個(gè)文件描述符,而epoll_create的size參數(shù)早在內(nèi)核版本2.6以后就已經(jīng)被忽略了,在早期的linux內(nèi)核版本中,該參數(shù)指定的是epoll模型創(chuàng)建時(shí),內(nèi)核數(shù)據(jù)結(jié)構(gòu)的初始大小,但現(xiàn)在這個(gè)參數(shù)已經(jīng)沒(méi)什么用了,因?yàn)閮?nèi)核會(huì)根據(jù)用戶的需要,自動(dòng)調(diào)整epoll模型的大小,epoll模型其實(shí)主要是紅黑樹(shù)+就緒隊(duì)列+底層的回調(diào)機(jī)制,這些都是內(nèi)核數(shù)據(jù)結(jié)構(gòu)。
雖然size沒(méi)什么用,但在給epoll_create傳參的時(shí)候,該參數(shù)必須大于0,我們隨便傳個(gè)128,256即可。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

3.
epoll_ctl的第一個(gè)參數(shù)就是epoll_create的返回值,也就是epoll模型的文件描述符,第二個(gè)參數(shù)代表你想使用epoll_ctl的什么功能,例如添加fd關(guān)心的事件,修改fd關(guān)心的事件,刪除fd關(guān)心的事件,可以傳宏EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL來(lái)表示使用epoll_ctl的什么功能,第三個(gè)參數(shù)是用戶要關(guān)心的fd,第四個(gè)參數(shù)是一個(gè)結(jié)構(gòu)體,其中包含了兩個(gè)字段,一個(gè)是32位大小的events,表示用戶告知內(nèi)核要關(guān)心fd上的什么事件,這些事件所對(duì)應(yīng)的宏如右圖源碼所示,每個(gè)宏的32個(gè)bit位只有一個(gè)為1,其他全為0,最常用的宏還是EPOLLIN和EPOLLOUT,另一個(gè)字段是一個(gè)聯(lián)合體data,這個(gè)聯(lián)合體中有一個(gè)重要的字段就是fd,同樣也是告訴內(nèi)核要關(guān)心的fd是什么。
epoll_ctl返回0代表接口調(diào)用成功,返回-1代表接口調(diào)用失敗。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

4.
第三個(gè)接口是epoll_wait,第一個(gè)參數(shù)也是epoll_create的返回值,epoll模型的文件描述符,第二個(gè)參數(shù)是純輸出型參數(shù),內(nèi)核會(huì)將就緒的struct epoll_event結(jié)構(gòu)體依次放入到這個(gè)數(shù)組中,第三個(gè)參數(shù)代表用戶傳入的events結(jié)構(gòu)體數(shù)組的大小,timeout代表epoll_wait監(jiān)視fd時(shí)的監(jiān)視策略,和poll的接口一樣,這里就不過(guò)多贅述了。
epoll_wait的返回值表示就緒的struct epoll_event結(jié)構(gòu)體的數(shù)量。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

2.epoll模型的底層原理

2.1 軟硬件交互時(shí),數(shù)據(jù)流動(dòng)的整個(gè)過(guò)程

1.
數(shù)據(jù)從軟件內(nèi)存中拷貝到硬件外設(shè),這個(gè)過(guò)程其實(shí)是比較好理解的,因?yàn)閿?shù)據(jù)可以貫穿協(xié)議棧,層層向下封裝報(bào)頭,最后由硬件對(duì)應(yīng)的驅(qū)動(dòng)程序?qū)?shù)據(jù)包交付給具體的硬件,協(xié)議棧的最底層就是物理層。
但數(shù)據(jù)到來(lái)時(shí),操作系統(tǒng)是怎么知道網(wǎng)絡(luò)中有數(shù)據(jù)到來(lái)了呢?這個(gè)我們之前從來(lái)沒(méi)有學(xué)過(guò),因?yàn)檫@屬于計(jì)組的知識(shí),我們搞軟件的學(xué)習(xí)他,其實(shí)只是為了理解數(shù)據(jù)在IO流動(dòng)時(shí)的整個(gè)過(guò)程。
當(dāng)數(shù)據(jù)到達(dá)網(wǎng)卡時(shí),網(wǎng)卡有相應(yīng)的8259中斷設(shè)備,該設(shè)備用于向CPU的某個(gè)針腳發(fā)送中斷信號(hào),CPU有很多的針腳,一部分的針腳會(huì)對(duì)應(yīng)一個(gè)硬件的中斷設(shè)備,當(dāng)CPU的針腳收到來(lái)自網(wǎng)卡中斷設(shè)備的中斷信號(hào)時(shí),該針腳就會(huì)被點(diǎn)亮,觸發(fā)高電平信號(hào),該針腳對(duì)應(yīng)的寄存器(CPU的工作臺(tái))里面會(huì)將該點(diǎn)亮的針腳解釋為二進(jìn)制序列,這個(gè)二進(jìn)制序列就是該針腳對(duì)應(yīng)的序號(hào)。
接下來(lái)CPU處理器就會(huì)根據(jù)這個(gè)序號(hào),查詢一個(gè)叫做中斷向量表的數(shù)據(jù)結(jié)構(gòu),中斷向量表在CPU啟動(dòng)的時(shí)候就已經(jīng)被加載到了內(nèi)存的特定位置,中斷向量表可以理解為一個(gè)數(shù)組結(jié)構(gòu),存儲(chǔ)著每個(gè)中斷序號(hào)所對(duì)應(yīng)的處理程序的入口地址,其實(shí)就是函數(shù)指針,而該函數(shù)內(nèi)部會(huì)回調(diào)網(wǎng)卡的驅(qū)動(dòng)方法,將數(shù)據(jù)從硬件網(wǎng)卡拷貝到內(nèi)存中的操作系統(tǒng)代碼內(nèi)部。(上面一整套的邏輯過(guò)程,全部都由操作系統(tǒng)來(lái)實(shí)現(xiàn))
至此就完成了數(shù)據(jù)從硬件到軟件內(nèi)存流動(dòng)的過(guò)程,數(shù)據(jù)到達(dá)操作系統(tǒng)內(nèi)部后,接下來(lái)的工作大家也很清楚,就是向上貫穿協(xié)議棧,拆分報(bào)頭和有效載荷,直到最后交給應(yīng)用層,軟件里面的數(shù)據(jù)流動(dòng)我們當(dāng)然是很熟悉的。

2.
計(jì)算機(jī)的硬件中,不僅僅只有網(wǎng)卡有終端設(shè)備,像比較常見(jiàn)的硬件鍵盤(pán),也有他自己的中斷設(shè)備,我們?cè)阪I盤(pán)上的每一次按鍵其實(shí)就會(huì)觸發(fā)一次硬件中斷。還有就是定時(shí)器模塊,他也有自己的中斷設(shè)備,可以在計(jì)算機(jī)整體的層面上,對(duì)內(nèi)核進(jìn)程進(jìn)行管理和調(diào)度。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

2.2 epoll模型內(nèi)核結(jié)構(gòu)圖

1.
當(dāng)你調(diào)用epoll_create時(shí),內(nèi)核會(huì)在底層創(chuàng)建一個(gè)epoll模型,該epoll模型主要由三個(gè)部分組成,紅黑樹(shù)+就緒隊(duì)列+底層的回調(diào)機(jī)制。
紅黑樹(shù)中的每個(gè)節(jié)點(diǎn)其實(shí)就是一個(gè)struct epoll_event結(jié)構(gòu)體,當(dāng)上層在調(diào)用epoll_ctl進(jìn)行添加fd關(guān)心的事件時(shí),其實(shí)就是向紅黑樹(shù)中插入節(jié)點(diǎn),所以epoll_ctl對(duì)于fd關(guān)心事件的增刪改,本質(zhì)其實(shí)就是對(duì)內(nèi)核中創(chuàng)建出來(lái)的紅黑樹(shù)進(jìn)行節(jié)點(diǎn)的增刪改,所以用戶告知內(nèi)核,你要幫我關(guān)心什么fd,底層就是對(duì)紅黑樹(shù)進(jìn)行管理。
就緒隊(duì)列中存放的是已經(jīng)就緒的struct epoll_event結(jié)構(gòu)體,內(nèi)核告知用戶哪些fd上的事件就緒時(shí),其實(shí)就是把就緒隊(duì)列中的每個(gè)節(jié)點(diǎn)依次拷貝到用戶調(diào)用epoll_wait時(shí),傳入的純輸出型參數(shù)結(jié)構(gòu)體數(shù)組events中。就緒隊(duì)列是一個(gè)雙向鏈表Doubly Linked List。
所以所謂的事件就緒的本質(zhì),其實(shí)就是將紅黑樹(shù)中的節(jié)點(diǎn)鏈入到就緒隊(duì)列中,鏈入的過(guò)程其實(shí)也很簡(jiǎn)單,只要在紅黑樹(shù)節(jié)點(diǎn)內(nèi)部多增加一個(gè)鏈表節(jié)點(diǎn)類型的指針即可,這個(gè)指針可以先初始化為nullptr,當(dāng)該節(jié)點(diǎn)中fd關(guān)心的事件就緒時(shí),再將這個(gè)指針指向就緒隊(duì)列中的尾部節(jié)點(diǎn)即可。
一個(gè)節(jié)點(diǎn)是可以同時(shí)在多個(gè)數(shù)據(jù)結(jié)構(gòu)當(dāng)中的,做法很簡(jiǎn)單,只要增加數(shù)據(jù)結(jié)構(gòu)中元素類型的指針即可,通過(guò)修改指針的指向就可以把節(jié)點(diǎn)鏈入到新的數(shù)據(jù)結(jié)構(gòu)里,在邏輯上我們把就緒隊(duì)列和紅黑樹(shù)分開(kāi)了,但在代碼實(shí)現(xiàn)上,只需要在struct epoll_event結(jié)構(gòu)體內(nèi)部增加指針就可以了,讓一個(gè)結(jié)構(gòu)體同時(shí)在就緒隊(duì)列和紅黑樹(shù)中。

2.
我們已經(jīng)知道epoll模型的大概原理了,但還有一個(gè)問(wèn)題,操作系統(tǒng)怎么知道紅黑樹(shù)上的哪些節(jié)點(diǎn)就緒了呢?難道操作系統(tǒng)也要遍歷整棵紅黑樹(shù),檢測(cè)每個(gè)節(jié)點(diǎn)的就緒情況?操作系統(tǒng)其實(shí)并不會(huì)這樣做,如果這樣做的話,那epoll還談?wù)撌裁锤咝兀磕鉫poll不也得遍歷所有的fd嗎?和我poll遍歷有什么區(qū)別呢?紅黑樹(shù)是查找的效率高,不是遍歷的效率高,如果遍歷所有的節(jié)點(diǎn),紅黑樹(shù)其實(shí)和鏈表遍歷在效率上是差不多的,一點(diǎn)都不高效!
那操作系統(tǒng)是怎么知道紅黑樹(shù)上的哪個(gè)節(jié)點(diǎn)就緒了呢?其實(shí)是通過(guò)底層的回調(diào)機(jī)制來(lái)實(shí)現(xiàn)的,這也是epoll接口公認(rèn)非常高效的重要的一個(gè)實(shí)現(xiàn)環(huán)節(jié)!
當(dāng)數(shù)據(jù)到達(dá)網(wǎng)卡時(shí),我們知道數(shù)據(jù)會(huì)經(jīng)過(guò)硬件中斷,CPU執(zhí)行中斷向量表等步驟來(lái)讓數(shù)據(jù)到達(dá)內(nèi)存中的操作系統(tǒng)內(nèi)部,在OS內(nèi)貫穿網(wǎng)絡(luò)協(xié)議棧時(shí),在傳輸層數(shù)據(jù)會(huì)被拷貝到struct file結(jié)構(gòu)體中的receive_queue接收緩沖區(qū)中,這個(gè)struct file結(jié)構(gòu)體對(duì)應(yīng)的文件描述符,其實(shí)就是accept上來(lái)的用于通信的sockfd,在這個(gè)結(jié)構(gòu)體內(nèi)部有一個(gè)非常重要的字段private_data,該指針會(huì)指向一個(gè)回調(diào)函數(shù),這個(gè)回調(diào)函數(shù)就會(huì)把該sock對(duì)應(yīng)的struct epoll_event結(jié)構(gòu)體鏈入到就緒隊(duì)列中,因?yàn)榇藭r(shí)數(shù)據(jù)已經(jīng)拷貝到內(nèi)核的socket接收緩沖區(qū)了,事件已經(jīng)就緒了,所以當(dāng)內(nèi)核在拷貝數(shù)據(jù)的同時(shí),還會(huì)調(diào)用private_data回調(diào)方法,將該sock對(duì)應(yīng)的紅黑樹(shù)節(jié)點(diǎn)鏈入到就緒隊(duì)列中,所以操作系統(tǒng)根本不用遍歷什么紅黑樹(shù)來(lái)檢測(cè)哪些節(jié)點(diǎn)是否就緒,當(dāng)數(shù)據(jù)到來(lái)時(shí),底層的回調(diào)機(jī)制會(huì)自動(dòng)將就緒的紅黑樹(shù)節(jié)點(diǎn)鏈入到就緒隊(duì)列里。

3
.總結(jié)一下fd事件就緒時(shí),底層的工作流程。
當(dāng)數(shù)據(jù)到達(dá)網(wǎng)絡(luò)設(shè)備網(wǎng)卡時(shí),會(huì)以硬件中斷作為發(fā)起點(diǎn),將中斷信號(hào)通過(guò)中斷設(shè)備發(fā)送到CPU的針腳,接下來(lái)CPU會(huì)查訊中斷向量表,找到中斷序號(hào)對(duì)應(yīng)的驅(qū)動(dòng)回調(diào)方法,在回調(diào)方法內(nèi)部會(huì)將數(shù)據(jù)從硬件設(shè)備網(wǎng)卡拷貝到軟件OS里。數(shù)據(jù)包在OS中會(huì)向上貫穿協(xié)議棧,到達(dá)傳輸層時(shí),數(shù)據(jù)會(huì)被拷貝到struct file的內(nèi)核緩沖區(qū)中,同時(shí)OS會(huì)執(zhí)行一個(gè)叫做private_data的回調(diào)函數(shù)指針字段,在該回調(diào)函數(shù)內(nèi)部會(huì)通過(guò)修改紅黑樹(shù)節(jié)點(diǎn)中的就緒隊(duì)列指針的內(nèi)容,將該節(jié)點(diǎn)鏈入到就緒隊(duì)列,內(nèi)核告知用戶哪些fd就緒時(shí),只需要將就緒隊(duì)列中的節(jié)點(diǎn)內(nèi)容拷貝到epoll_wait的輸出型參數(shù)events即可,這就是epoll模型的底層回調(diào)機(jī)制!

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

4.
下面我稍微模擬了一下private_data指針回調(diào)的方式,可以用該指針存儲(chǔ)一個(gè)函數(shù)指針,在回調(diào)時(shí),只需要先將指針的類型從void類型轉(zhuǎn)換為函數(shù)指針的類型,然后再調(diào)用即可。
而所謂的epoll模型其實(shí)就是紅黑樹(shù)+就緒隊(duì)列+底層的回調(diào)機(jī)制。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

2.3 關(guān)于epoll模型所產(chǎn)生的問(wèn)題

1.為什么說(shuō)epoll模型是高效的呢?

因?yàn)榇蟛糠值墓ぷ鞑僮飨到y(tǒng)都幫我們做了,比如添加節(jié)點(diǎn)到紅黑樹(shù),我們只需要調(diào)用epoll_ctrl即可,返回就緒的fd,直接相當(dāng)于返回就緒隊(duì)列中的節(jié)點(diǎn)即可,上層直接就可以拿到就緒的fd,檢測(cè)是否就緒的工作也不用遍歷,而是當(dāng)?shù)讓訑?shù)據(jù)就緒時(shí),會(huì)有回調(diào)機(jī)制自動(dòng)將紅黑樹(shù)的節(jié)點(diǎn)鏈入到就緒隊(duì)列中,操作系統(tǒng)也無(wú)須遍歷紅黑樹(shù)進(jìn)行就緒檢測(cè),上層在拿到就緒的fd后,可以確定范圍的遍歷輸出型參數(shù)struct epoll_event數(shù)組,而不是盲目的遍歷整個(gè)數(shù)組的所有元素。

2.為什么選用紅黑樹(shù)作為epoll模型的底層數(shù)據(jù)結(jié)構(gòu)?
因?yàn)榧t黑樹(shù)的搜索效率非常的高,可以達(dá)到logN的時(shí)間復(fù)雜度,所以無(wú)論是epoll_ctl的插入,刪除還是修改,這些工作的首要前提是先找到目標(biāo)節(jié)點(diǎn)或目標(biāo)位置,找到之后,再進(jìn)行具體的操作,而找到這一步紅黑樹(shù)的效率就非常的高。
有人可能會(huì)說(shuō)紅黑樹(shù)需要旋轉(zhuǎn)調(diào)整平衡啊,雖然在邏輯上我們感覺(jué)紅黑樹(shù)的旋轉(zhuǎn)調(diào)平衡很費(fèi)時(shí)間,可能會(huì)造成紅黑樹(shù)的效率降低,但其實(shí)并不是這樣的,所謂的旋轉(zhuǎn)調(diào)平衡只是在邏輯上復(fù)雜而已,在實(shí)際操作上僅僅只是修改節(jié)點(diǎn)內(nèi)的指針而已,對(duì)紅黑樹(shù)的效率影響并不大。
同時(shí)紅黑樹(shù)對(duì)于平衡的要求并沒(méi)有AVL高,所以在旋轉(zhuǎn)調(diào)平衡的次數(shù)上,紅黑樹(shù)要比AVL樹(shù)少很多,在整體效率上是要比AVL樹(shù)高的,這也是使用紅黑樹(shù),不使用AVL樹(shù)的原因。

3.epoll_wait有哪些細(xì)節(jié)?
(1)epoll_wait會(huì)將所有就緒的fd,依次按照順序放到輸出型參數(shù)events中,用戶在遍歷數(shù)組處理就緒的事件時(shí),無(wú)須遍歷多余的任何一個(gè)fd,只需要遍歷從0到epoll_wait的返回值個(gè)fd即可。
(2)如果就緒隊(duì)列的節(jié)點(diǎn)數(shù)量很多,epoll_wait的輸出型參數(shù)數(shù)組一次拿不完也不用擔(dān)心,因?yàn)殛?duì)列是先進(jìn)先出,下一次在調(diào)用epoll_wait時(shí),再拿就緒的事件也可以。
(3)select poll在使用的時(shí)候,都需要程序員自己維護(hù)一個(gè)第三方數(shù)組來(lái)存儲(chǔ)用戶關(guān)心的fd及事件,但epoll不需要,因?yàn)閮?nèi)核為epoll在底層維護(hù)了一棵紅黑樹(shù),用戶直接通過(guò)epoll_ctl來(lái)對(duì)紅黑樹(shù)的節(jié)點(diǎn)進(jìn)行增刪改即可,無(wú)須自己在應(yīng)用層維護(hù)第三方的數(shù)組。

3.epoll服務(wù)器代碼編寫(xiě)

1.
初始化模塊實(shí)現(xiàn)起來(lái)也非常的簡(jiǎn)單,先正常進(jìn)行服務(wù)器的socket創(chuàng)建,bind綁定,listen監(jiān)聽(tīng),接下來(lái)就是創(chuàng)建epoll模型,創(chuàng)建成功之后,將listensock的讀事件添加到epoll模型的紅黑樹(shù)中,添加的方式也很簡(jiǎn)單,只要定義一個(gè)struct epoll_event結(jié)構(gòu)體,將其中的events和data.fd字段填充好,調(diào)用epoll_ctl即可將listensock及其事件添加到紅黑樹(shù)中。下一步就是申請(qǐng)epoll_wait的輸出型參數(shù)的空間,只需要new一下即可。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

2.
服務(wù)器start的邏輯也很簡(jiǎn)單,先調(diào)用epoll_wait進(jìn)行fd的監(jiān)視,當(dāng)返回值大于0時(shí),調(diào)用接口HandlerEvent進(jìn)行時(shí)間的處理,由于今天只處理讀事件,所以只需要兩個(gè)分支語(yǔ)句就可以實(shí)現(xiàn),HandlerEvent的參數(shù)為readyNum,表示就緒的事件個(gè)數(shù),遍歷_revs數(shù)組時(shí),只需要遍歷0-readyNum個(gè)結(jié)構(gòu)體即可,不用遍歷任何一個(gè)多余的fd。
然后在accept的分支語(yǔ)句中,只需要將就緒的連接拿上來(lái)即可,然后把sock設(shè)置到紅黑樹(shù)即可,等待下一次就緒時(shí)recv sock上的數(shù)據(jù)。
在recv的分支語(yǔ)句中,只讀取一次的話,其實(shí)和前面的兩個(gè)服務(wù)器代碼一樣,還是存在黏包問(wèn)題的,下一篇文章Reactor會(huì)解決所有的問(wèn)題。需要提醒一下的是,建議先從紅黑樹(shù)中移除節(jié)點(diǎn),然后再關(guān)閉sock,如果你先關(guān)閉了sock,則fd就會(huì)變?yōu)闊o(wú)效,如果此時(shí)調(diào)用epoll_ctl移除節(jié)點(diǎn),傳入的參數(shù)sock就是無(wú)效的,則此時(shí)epoll_ctl會(huì)報(bào)錯(cuò)!
(epoll_server寫(xiě)起來(lái)是不很簡(jiǎn)單呢?因?yàn)樵礁咝У慕涌?,需要程序員做的事情就會(huì)越少,內(nèi)核做的事情會(huì)越多,代碼編寫(xiě)的成本就會(huì)低一些。)

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

下面是完整的epoll_server代碼
【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

下面是服務(wù)器的調(diào)用邏輯,和之前的select poll沒(méi)有什么區(qū)別,還是很簡(jiǎn)單的。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器

4.總結(jié)select poll epoll的優(yōu)缺點(diǎn)

select缺點(diǎn):

(1)支持的文件描述符有上限,我的內(nèi)核版本下最大是1024
(2)需要程序員自己維護(hù)一個(gè)第三方數(shù)組來(lái)存儲(chǔ)用戶關(guān)心的fd及事件
(3)由于輸入和輸出耦合,導(dǎo)致每次調(diào)用select前都需要向select重新設(shè)置關(guān)心的fd及事件
(4)用戶需要每次遍歷整個(gè)fd_set位圖,來(lái)判斷哪個(gè)就緒的fd需要處理,如果你有一個(gè)非常大的文件描述符集合,即使只有一個(gè)文件描述符就緒,你也需要檢查所有的位。內(nèi)核也需要每次遍歷fd_set位圖,來(lái)判斷哪個(gè)fd就緒。用戶與內(nèi)核大量的遍歷fd_set集合會(huì)帶來(lái)效率的降低。
select優(yōu)點(diǎn):
(1)能夠同時(shí)監(jiān)聽(tīng)多個(gè)文件描述符,使得一個(gè)進(jìn)程或線程能夠同時(shí)管理多個(gè)IO操作,提升IO的效率
(2)select 是一個(gè)跨平臺(tái)的系統(tǒng)調(diào)用,幾乎在所有主流操作系統(tǒng)上都得到支持,包括 Linux、Unix、Windows 等


poll缺點(diǎn):

(1)需要程序員自己維護(hù)一個(gè)第三方結(jié)構(gòu)體數(shù)組來(lái)存儲(chǔ)用戶關(guān)心的fd及事件
(2)與select相同的是,用戶仍然需要遍歷整個(gè)數(shù)組來(lái)找出就緒的文件描述符,哪怕只有一個(gè)結(jié)構(gòu)體的revents是就緒的,來(lái)判斷哪個(gè)是就緒的fd從而進(jìn)行處理。內(nèi)核也需要每次遍歷結(jié)構(gòu)體數(shù)組,來(lái)判斷哪個(gè)fd是就緒的。用戶與內(nèi)核大量的遍歷集合會(huì)帶來(lái)效率的降低。

poll優(yōu)點(diǎn):
(1)一個(gè)進(jìn)程最多能打開(kāi)多少fd,poll就能最多同時(shí)監(jiān)視多少fd(數(shù)學(xué)上限為2^64個(gè))
(2)不需要每次在調(diào)用poll之前重新設(shè)置關(guān)心的fd及事件。
(3)poll跨平臺(tái)移植性差


epoll缺點(diǎn):
(1)epoll不適用于小規(guī)模的連接,因?yàn)閑poll需要維護(hù)很多的內(nèi)核數(shù)據(jù)結(jié)構(gòu),更適用于高并發(fā)大規(guī)模的IO操作,小規(guī)模的連接會(huì)由于epoll維護(hù)復(fù)雜的數(shù)據(jù)結(jié)構(gòu)和回調(diào)機(jī)制等,從而給系統(tǒng)帶來(lái)不必要的開(kāi)銷
(2) epoll跨平臺(tái)移植性差

epoll優(yōu)點(diǎn):
(1)一個(gè)進(jìn)程最多能打開(kāi)多少fd,epoll就能最多同時(shí)監(jiān)視多少fd.(數(shù)學(xué)上限為2^32個(gè))
(2)不需要程序員自己維護(hù)第三方數(shù)組來(lái)存儲(chǔ)用戶關(guān)心的fd及事件,因?yàn)閮?nèi)核會(huì)為epoll創(chuàng)建一棵紅黑樹(shù),直接向紅黑樹(shù)進(jìn)行節(jié)點(diǎn)的增刪改即可。
(3)用戶同樣也需要遍歷結(jié)構(gòu)體數(shù)組,因?yàn)閑poll_wait會(huì)將就緒的fd依次有順序的放到用戶傳入的結(jié)構(gòu)體數(shù)組events中,所以用戶是可以按需遍歷的。但內(nèi)核不需要遍歷整棵紅黑樹(shù)來(lái)檢測(cè)哪些節(jié)點(diǎn)上的fd就緒了,因?yàn)閑poll模型有他自己的底層回調(diào)機(jī)制,大大減少內(nèi)核遍歷集合所帶來(lái)的性能開(kāi)銷,從而提高了效率。
(4)不需要每次在調(diào)用epoll前重新設(shè)置關(guān)心的fd及事件。

其實(shí)我個(gè)人認(rèn)為,內(nèi)核是可以做到讓select和poll在使用時(shí),程序員也按需遍歷就緒的fd的,而不用每次都全部遍歷存放fd的數(shù)組或位圖。內(nèi)核一定是可以做到的,只不過(guò)可能當(dāng)時(shí)設(shè)計(jì)內(nèi)核的人不愿意,不想這么干,或者暫時(shí)內(nèi)核還做不到,但他們一定考慮過(guò)這個(gè)按需遍歷的問(wèn)題。
select、poll的內(nèi)核在底層的做法也是和上層一樣的,都是直接遍歷整個(gè)數(shù)組,無(wú)論你上層向集合中設(shè)置了多少fd。

【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll,Linux,linux,運(yùn)維,服務(wù)器文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-701538.html

到了這里,關(guān)于【Linux】高級(jí)IO --- 多路轉(zhuǎn)接,select,poll,epoll的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【Linux】IO多路轉(zhuǎn)接——poll接口

    【Linux】IO多路轉(zhuǎn)接——poll接口

    目錄 poll初識(shí) poll函數(shù) poll服務(wù)器 poll的優(yōu)點(diǎn) poll的缺點(diǎn) poll也是系統(tǒng)提供的一個(gè)多路轉(zhuǎn)接接口。 poll系統(tǒng)調(diào)用也可以讓我們的程序同時(shí)監(jiān)視多個(gè)文件描述符上的事件是否就緒,和select的定位是一樣的,適用場(chǎng)景也是一樣的。 poll函數(shù) poll函數(shù)的函數(shù)原型如下: 參數(shù)說(shuō)明: fds:一

    2024年02月12日
    瀏覽(16)
  • 【Linux】IO多路轉(zhuǎn)接技術(shù)Epoll的使用

    【Linux】IO多路轉(zhuǎn)接技術(shù)Epoll的使用

    ? 在學(xué)習(xí) epoll 之前,我們首先了解一下Linux中的多路復(fù)用技術(shù): 在Linux系統(tǒng)中, IO多路復(fù)用 是一種重要的技術(shù),它允許一個(gè)進(jìn)程同時(shí)監(jiān)視多個(gè)文件描述符,一旦某個(gè)描述符準(zhǔn)備好進(jìn)行讀?。ㄍǔJ亲x就緒或?qū)懢途w),內(nèi)核會(huì)通知該進(jìn)程進(jìn)行相應(yīng)的讀寫(xiě)操作。這樣,我們可以

    2024年04月27日
    瀏覽(15)
  • IO多路復(fù)用之select/poll/epoll

    IO多路復(fù)用之select/poll/epoll

    掌握select編程模型,能夠?qū)崿F(xiàn)select版本的TCP服務(wù)器. 掌握poll編程模型,能夠?qū)崿F(xiàn)poll版本的TCP服務(wù)器. 掌握epoll的編程模型,能夠?qū)崿F(xiàn)epoll版本的TCP服務(wù)器. epoll的LT模式和ET模式. 理解select和epoll的優(yōu)缺點(diǎn)對(duì)比. 提示:以下是本篇文章正文內(nèi)容,下面案例可供參考 多路轉(zhuǎn)接天然的是讓我

    2023年04月09日
    瀏覽(20)
  • 【網(wǎng)絡(luò)】多路轉(zhuǎn)接——poll | epoll

    【網(wǎng)絡(luò)】多路轉(zhuǎn)接——poll | epoll

    ??作者:一只大喵咪1201 ??專欄:《網(wǎng)絡(luò)》 ??格言: 你只管努力,剩下的交給時(shí)間! 書(shū)接上文五種IO模型 | select。 poll 也是一種多路轉(zhuǎn)接的方案,它專門(mén)用來(lái)解決 select 的兩個(gè)問(wèn)題: 等待fd有上限的問(wèn)題。 每次調(diào)用都需要重新設(shè)置 fd_set 的問(wèn)題。 如上圖所示便是 poll 系統(tǒng)調(diào)

    2024年02月10日
    瀏覽(24)
  • 【高并發(fā)網(wǎng)絡(luò)通信架構(gòu)】引入IO多路復(fù)用(select,poll,epoll)實(shí)現(xiàn)高并發(fā)tcp服務(wù)端

    【高并發(fā)網(wǎng)絡(luò)通信架構(gòu)】引入IO多路復(fù)用(select,poll,epoll)實(shí)現(xiàn)高并發(fā)tcp服務(wù)端

    目錄 一,往期文章 二,基本概念 IO多路復(fù)用 select 模型 poll 模型 epoll 模型 select,poll,epoll 三者對(duì)比 三,函數(shù)清單 1.select 方法 2.fd_set 結(jié)構(gòu)體 3.poll 方法 4.struct pollfd 結(jié)構(gòu)體 5.epoll_create 方法 6.epoll_ctl 方法 7.epoll_wait 方法 8.struct epoll_event 結(jié)構(gòu)體 四,代碼實(shí)現(xiàn) select 操作流程 s

    2024年02月12日
    瀏覽(31)
  • 【高并發(fā)網(wǎng)絡(luò)通信架構(gòu)】3.引入IO多路復(fù)用(select,poll,epoll)實(shí)現(xiàn)高并發(fā)tcp服務(wù)端

    【高并發(fā)網(wǎng)絡(luò)通信架構(gòu)】3.引入IO多路復(fù)用(select,poll,epoll)實(shí)現(xiàn)高并發(fā)tcp服務(wù)端

    目錄 一,往期文章 二,基本概念 IO多路復(fù)用 select 模型 poll 模型 epoll 模型 select,poll,epoll 三者對(duì)比 三,函數(shù)清單 1.select 方法 2.fd_set 結(jié)構(gòu)體 3.poll 方法 4.struct pollfd 結(jié)構(gòu)體 5.epoll_create 方法 6.epoll_ctl 方法 7.epoll_wait 方法 8.struct epoll_event 結(jié)構(gòu)體 四,代碼實(shí)現(xiàn) select 操作流程 s

    2024年02月14日
    瀏覽(26)
  • Linux 多路轉(zhuǎn)接 —— poll

    Linux 多路轉(zhuǎn)接 —— poll

    小編是雙非本科大二菜鳥(niǎo)不贅述,歡迎米娜桑來(lái)指點(diǎn)江山哦 1319365055 ????非科班轉(zhuǎn)碼社區(qū)誠(chéng)邀您入駐???? 小伙伴們,滿懷希望,所向披靡,打碼一路向北 一個(gè)人的單打獨(dú)斗不如一群人的砥礪前行 這是和夢(mèng)想合伙人組建的社區(qū),誠(chéng)邀各位有志之士的加入?。?社區(qū)用戶好文

    2024年02月10日
    瀏覽(18)
  • 【Linux】多路轉(zhuǎn)接 -- epoll

    【Linux】多路轉(zhuǎn)接 -- epoll

    epoll系統(tǒng)調(diào)用和select以及poll是一樣的,都是可以讓我們的程序同時(shí)監(jiān)視多個(gè)文件描述符上的事件是否就緒。 epoll在命名上比poll多了一個(gè)poll,這個(gè)e可以理解為extend, epoll就是為了同時(shí)處理大量文件描述符而改進(jìn)的poll。 epoll在2.5.44內(nèi)核中被引進(jìn),它幾乎具備了select和poll的所有

    2024年02月14日
    瀏覽(16)
  • Linux 多路轉(zhuǎn)接 —— select

    Linux 多路轉(zhuǎn)接 —— select

    小編是雙非本科大二菜鳥(niǎo)不贅述,歡迎米娜桑來(lái)指點(diǎn)江山哦 1319365055 ????非科班轉(zhuǎn)碼社區(qū)誠(chéng)邀您入駐???? 小伙伴們,滿懷希望,所向披靡,打碼一路向北 一個(gè)人的單打獨(dú)斗不如一群人的砥礪前行 這是和夢(mèng)想合伙人組建的社區(qū),誠(chéng)邀各位有志之士的加入??! 社區(qū)用戶好文

    2024年02月08日
    瀏覽(14)
  • linux poll,epoll,select的區(qū)別

    epoll中紅黑樹(shù)的作用? 紅黑樹(shù)(rbtree)、以及epoll的實(shí)現(xiàn)原理_epoll 紅黑樹(shù)_For Nine的博客-CSDN博客 紅黑樹(shù)和epoll_wait的關(guān)系? epoll_wait/就緒list和紅黑樹(shù)的關(guān)系 - 知乎 其他區(qū)別: 1. select 在linux內(nèi)核中限制了能監(jiān)聽(tīng)的數(shù)目上限。32位是1024,64位是2048 2. poll是將監(jiān)聽(tīng)的對(duì)象改成了鏈表

    2024年02月03日
    瀏覽(18)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包