一、什么是io多路復(fù)用
在bio模型中,一個(gè)io請(qǐng)求對(duì)應(yīng)一個(gè)線程,造成線程極大浪費(fèi),且沒(méi)有數(shù)據(jù)發(fā)送的情況下,線程也一直阻塞等待,資源利用率不高。
在nio模型中,多個(gè)io請(qǐng)求歸一個(gè)線程管理,當(dāng)io就緒的時(shí)候,線程通知應(yīng)用程序進(jìn)行數(shù)據(jù)讀寫,這樣不用創(chuàng)建很多線程,線程也不用一直阻塞等待io就緒,這樣的技術(shù)稱為io多路復(fù)用。
io多路復(fù)用通過(guò)一種機(jī)制,可以監(jiān)視多個(gè)文件描述符,一旦某個(gè)文件描述就緒(),能夠通知應(yīng)用程序進(jìn)行相應(yīng)的io讀寫操作。
二、為什么用io多路復(fù)用
io多路復(fù)用解決了多線程io阻塞問(wèn)題,避免創(chuàng)建很多線程,及降低線程上下文切換的開銷,提升資源利用率。
三、io多路復(fù)用模型原理分析
監(jiān)視多個(gè)文件描述符的機(jī)制不同,io多路復(fù)用技術(shù)主要有,poll,epoll,本質(zhì)上都是阻塞io,因?yàn)樗麄兌夹枰趇o就緒后線程標(biāo)識(shí)符 有什么用,自己負(fù)責(zé)進(jìn)行數(shù)據(jù)在用戶空間和內(nèi)核空間之間copy。非阻塞io無(wú)需自己負(fù)責(zé)數(shù)據(jù)copy,操作系統(tǒng)會(huì)把數(shù)據(jù)從內(nèi)核空間copy到用戶空間。
1.:int (int nfds, *, *, *, *);
監(jiān)視多個(gè)文件描述符的屬性變化(可讀、可寫、錯(cuò)誤異常)。監(jiān)視的文件描述符有3類:,,,調(diào)用 線程會(huì)阻塞,知道有描述符就緒,才返回,之后通過(guò)遍歷fdset來(lái)找到就緒的描述符。
nfds: 要監(jiān)視的文件描述符的范圍,在 Linux 上最大值一般為1024。
: 監(jiān)視的可讀描述符集合,只要有文件描述符即將進(jìn)行讀操作,這個(gè)文件描述符就存儲(chǔ)到這。
: 監(jiān)視的可寫描述符集合,只要有文件描述符即將進(jìn)行寫操作,這個(gè)文件描述符就存儲(chǔ)到這里。
: 監(jiān)視的錯(cuò)誤異常描述符集合。
: 超時(shí)時(shí)間,它告知內(nèi)核等待任何一個(gè)文件描述符就緒可花多少時(shí)間。有3中設(shè)置:
1)永遠(yuǎn)等待,僅有一個(gè)描述符就緒后返回;
2)不等待,檢查所有描述符看其是否就緒,是一種輪訓(xùn)方式,返回就緒的個(gè)數(shù);
3)等待固定時(shí)間,返回就緒的個(gè)數(shù)。
優(yōu)點(diǎn):所有平臺(tái)都支持,不存在兼容問(wèn)題。
缺點(diǎn):1)每次調(diào)用,都要把fe集合從用戶態(tài)copy到內(nèi)核態(tài),fd很多時(shí),開銷很大。同時(shí)在內(nèi)核態(tài)要遍歷fd,看其是否就緒,開銷也很大。
2.poll:int poll( fds, nfds, int );
poll的本質(zhì)和一樣,沒(méi)有太大差別,管理多個(gè)描述符也是進(jìn)行輪詢,根據(jù)描述符狀態(tài)進(jìn)行處理,但poll沒(méi)有最大描述符個(gè)數(shù)限制(限制1024個(gè)),但是隨著數(shù)量的增加,性能也有下降。poll() 和 () 同樣存在一個(gè)缺點(diǎn)就是,包含大量文件描述符的數(shù)組被整體復(fù)制于用戶態(tài)和內(nèi)核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨著文件描述符數(shù)量的增加而線性增大。
fds:不同于 () ,使用三個(gè)位置來(lái)表示三個(gè) fd集合,poll() 使用一個(gè) 的指針,指向一個(gè) 結(jié)構(gòu)體數(shù)組,結(jié)構(gòu)體中包括了要監(jiān)視的文件描述符和事件,感興趣的事件由結(jié)構(gòu)中 來(lái)確定,調(diào)用后實(shí)際發(fā)生的時(shí)間將被填寫在結(jié)構(gòu)體的 域中,如下:
{
int fd;/ 文件描述符 /
short ; / 感興趣的事件 /
short ; / 實(shí)際發(fā)生了的事件 */
};
nfds: 指定第一個(gè)參數(shù)數(shù)組元素個(gè)數(shù),并不是限制監(jiān)視的文件描述符個(gè)數(shù),poll不限制文件描述符個(gè)數(shù)。
: 指定等待的毫秒數(shù),無(wú)論 I/O 是否就緒,poll() 都會(huì)返回。當(dāng)?shù)却龝r(shí)間為 0 時(shí),poll() 函數(shù)立即返回,為 -1 則使 poll() 一直阻塞直到一個(gè)感興趣的事件發(fā)生后返回。
poll() 返回結(jié)構(gòu)體中 域不為 0 的文件描述符個(gè)數(shù);如果在超時(shí)前沒(méi)有任何事件發(fā)生,poll()返回 0;
返回-1說(shuō)明有錯(cuò)誤發(fā)生,具體錯(cuò)誤有以下集中:
EBADF:一個(gè)或多個(gè)結(jié)構(gòu)體中指定的文件描述符無(wú)效。
:fds 指針指向的地址超出進(jìn)程的地址空間。
EINTR:請(qǐng)求的事件之前產(chǎn)生一個(gè)信號(hào),調(diào)用可以重新發(fā)起。
:nfds 參數(shù)超出 值。
:可用內(nèi)存不足,無(wú)法完成請(qǐng)求。
poll和非常類似,只是fd集合的方式不同,poll使用結(jié)構(gòu)體數(shù)組,用set,其他都差不多。
3.epoll:
int (int size);創(chuàng)建一個(gè)epoll文件描述符,管理其他多個(gè)文件描述符。
size: 用來(lái)告訴內(nèi)核這個(gè)監(jiān)聽的數(shù)目一共有多大,參數(shù) size 并不是限制了 epoll 所能監(jiān)聽的描述符最大個(gè)數(shù),只是對(duì)內(nèi)核初始分配內(nèi)部數(shù)據(jù)結(jié)構(gòu)的一個(gè)建議。當(dāng)創(chuàng)建好 epoll 描述符后,它就是會(huì)占用一個(gè) fd 值,在使用完 epoll 后,必須調(diào)用 close() 關(guān)閉,否則可能導(dǎo)致 fd 被耗盡。
int (int epfd, int op, int fd, *event);事件注冊(cè),它不同于 () 是在遍歷描述符時(shí)告訴內(nèi)核要關(guān)注什么事件,而是在這里先注冊(cè)要關(guān)注的事件。
epfd: epoll 專用的文件描述符,()的返回值
op: 表示動(dòng)作,用三個(gè)宏來(lái)表示:
:注冊(cè)新的 fd 到 epfd 中;
:修改已經(jīng)注冊(cè)的fd的監(jiān)聽事件;
:從 epfd 中刪除一個(gè) fd;
fd: 需要監(jiān)視的文件描述符
event: 告訴內(nèi)核要監(jiān)聽什么事件,主要有以下事件:
:表示對(duì)應(yīng)的文件描述符可以讀(包括對(duì)端 正常關(guān)閉);
:表示對(duì)應(yīng)的文件描述符可以寫;
:表示對(duì)應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀;
:表示對(duì)應(yīng)的文件描述符發(fā)生錯(cuò)誤;
:表示對(duì)應(yīng)的文件描述符被掛斷;
:將 EPOLL 設(shè)為邊緣觸發(fā)(Edge )模式,這是相對(duì)于水平觸發(fā)(Level )來(lái)說(shuō)的。
:只監(jiān)聽一次事件,當(dāng)監(jiān)聽完這次事件之后,如果還需要繼續(xù)監(jiān)聽這個(gè) 的話,需要再次把這個(gè) 加入到 EPOLL 隊(duì)列里。
int ( int epfd, * , int , int );注冊(cè)在epoll中已經(jīng)發(fā)生的事件,類似于 () 調(diào)用,輪訓(xùn)事件表,看是否有事件發(fā)生。
epfd: epoll 專用的文件描述符,()的返回值
:epoll 將會(huì)把發(fā)生的事件添加到 數(shù)組中, 不可以是空指針,內(nèi)核只負(fù)責(zé)把數(shù)據(jù)復(fù)制到這個(gè) 數(shù)組中,不會(huì)去幫助我們?cè)谟脩魬B(tài)中分配內(nèi)存。
: 告之內(nèi)核這個(gè) 有多大。
: 超時(shí)時(shí)間,為 -1 時(shí),函數(shù)為阻塞,知道有事件發(fā)生。
返回就緒的文件描述符個(gè)數(shù)。
epoll 對(duì)文件描述符的操作有兩種模式:LT(level :水平觸發(fā)模式,)和 ET(edge :邊沿觸發(fā)模式)。
LT模式:當(dāng) 檢測(cè)到描述符事件發(fā)生并將此事件通知應(yīng)用程序后線程標(biāo)識(shí)符 有什么用,應(yīng)用程序可以不處理該事件,下次調(diào)用 時(shí),會(huì)再次向應(yīng)用程序通知此事件,直到事件被處理,可能會(huì)重復(fù)處理事件。
ET 模式:當(dāng) 檢測(cè)到描述符事件發(fā)生并將此事件通知應(yīng)用程序后,應(yīng)用程序必須立即處理該事件,如果不處理,下次調(diào)用 時(shí),不會(huì)再次向應(yīng)用程序通知此事件,可能會(huì)丟失事件處理。
在 /poll中,內(nèi)核對(duì)所有監(jiān)視的文件描述符進(jìn)行掃描,返回就緒的個(gè)數(shù);而 epoll() 事先通過(guò) () 來(lái)注冊(cè)一個(gè)文件描述符及感興趣的事件,一旦某個(gè)文件描述符上感興趣的事件發(fā)生后,內(nèi)核會(huì)采用類似 的回調(diào)機(jī)制,將發(fā)生的事件填入()的結(jié)構(gòu)中,當(dāng)進(jìn)程調(diào)用 () 時(shí)便得到就緒的個(gè)數(shù)。
優(yōu)點(diǎn):
1.監(jiān)視的描述符數(shù)量不受限制。
2.效率不會(huì)隨著監(jiān)視 fd 的數(shù)量的增長(zhǎng)而下降。(),poll() 需要自己不斷輪詢所有 fd 集合來(lái)發(fā)現(xiàn)就緒設(shè)備,當(dāng)fd集合很大時(shí),開銷很大。而epoll是通過(guò)事件回調(diào)機(jī)制發(fā)現(xiàn)就緒的設(shè)備,當(dāng)設(shè)備就緒后,回進(jìn)行回調(diào),不需要輪訓(xùn)。
3.(),poll() 每次調(diào)用都要把 fd 集合從用戶態(tài)往內(nèi)核態(tài)拷貝一次,而epoll通過(guò)內(nèi)核與用戶空間共享一塊內(nèi)存,避免了無(wú)畏的內(nèi)存拷貝(mmap機(jī)制)。