I/O 多路復(fù)用之select、poll、epoll詳解

## [同步 I/O 三種方式(select,poll,epoll)](id:)

---

> **select,poll,epoll 都是 IO 多路復(fù)用的機(jī)制。**I/O 多路復(fù)用就是通過一種機(jī)制,一個(gè)進(jìn)程可以監(jiān)視多個(gè)描述符,一旦某個(gè)描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進(jìn)行相應(yīng)的讀寫操作。但 **select,poll,epoll 本質(zhì)上都是同步 I/O**,因?yàn)?*他們都需要在讀寫事件就緒后自己負(fù)責(zé)進(jìn)行讀寫**,也就是說**這個(gè)讀寫過程是阻塞的**,而**異步 I/O 則無需自己負(fù)責(zé)進(jìn)行讀寫,異步 I/O 的實(shí)現(xiàn)會(huì)負(fù)責(zé)把數(shù)據(jù)從內(nèi)核拷貝到用戶空間。**

## [select](id:)

----

> **`int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);`**

> - select 函數(shù)監(jiān)視的文件描述符分3類,分別是 readfds、writefds、和 exceptfds。調(diào)用后 select 函數(shù)會(huì)阻塞,直到有描述副就緒(有數(shù)據(jù) 可讀、可寫、或者有 except),或者超時(shí)(timeout 指定等待時(shí)間,如果立即返回設(shè)為 null 即可),函數(shù)返回。當(dāng) select 函數(shù)返回后,可以 通過遍歷 fdset,來找到就緒的描述符。

> - select 目前幾乎在所有的平臺(tái)上支持,其良好跨平臺(tái)支持也是它的一個(gè)優(yōu)點(diǎn)。select 的一 個(gè)缺點(diǎn)在于單個(gè)進(jìn)程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,在 Linux 上一般為1024,可以通過修改宏定義甚至重新編譯內(nèi)核的方式提升這一限制,但是這樣也會(huì)造成效率的降低。

## [poll](id:)

----

> **`int poll (struct pollfd *fds, unsigned int nfds, int timeout);`**

> - 不同與 select 使用三個(gè)位圖來表示三個(gè) fdset 的方式,poll 使用一個(gè) pollfd 的指針實(shí)現(xiàn)。

> **`struct pollfd {`**

????**`int fd; /* file descriptor(文件描述符) */ `**

????**`short events; /* requested events to watch(請(qǐng)求監(jiān)視的事件) */ `**

????**`short revents; /* returned events witnessed(返回事件) */ `**

**`};`**

> - pollfd 結(jié)構(gòu)包含了要監(jiān)視的 event 和發(fā)生的 event,不再使用 select “參數(shù)-值”傳遞的方式。同時(shí),pollfd 并沒有最大數(shù)量限制(但是數(shù)量過大后性能也是會(huì)下降)。和 select 函數(shù)一樣,poll 返回后,需要輪詢 pollfd 來獲取就緒的描述符。

> - 從上面看,select 和 poll 都需要在返回后,通過遍歷文件描述符來獲取已經(jīng)就緒的 socket。事實(shí)上,同時(shí)連接的大量客戶端在一時(shí)刻可能只有很少的處于就緒狀態(tài),因此隨著監(jiān)視的描述符數(shù)量的增長,其效率也會(huì)線性下降。

## [epoll](id:)

---

> **epoll 是在2.6內(nèi)核中提出的,是之前的 select 和 poll 的增強(qiáng)版本。相對(duì)于 select 和 poll 來說,epoll 更加靈活,沒有描述符限制。epoll 使用一個(gè)文件描述符管理多個(gè)描述符,將用戶關(guān)系的文件描述符的事件存放到內(nèi)核的一個(gè)事件表中,這樣在用戶空間和內(nèi)核空間的 copy 只需一次。**

**epoll操作過程需要三個(gè)接口,分別如下:**

> `1. int epoll_create(int size);` **`//創(chuàng)建一個(gè)epoll的句柄,size用來告訴內(nèi)核這個(gè)監(jiān)聽的數(shù)目一共有多大`**

> **此處的 size 參數(shù)不同于 select() 中的第一個(gè)參數(shù),給出最大監(jiān)聽的 fd+1 的值,參數(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 值,在 linux 下如果查看 /proc/進(jìn)程id/fd/,是能夠看到這個(gè) fd 的,所以在使用完 epoll 后,必須調(diào)用 close() 關(guān)閉,否則可能導(dǎo)致 fd 被耗盡。

> `2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);`**`//對(duì)指定描述符fd執(zhí)行op操作`**

> `3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);` **`//等待epfd上的io事件,最多返回maxevents個(gè)事件`**

## [總結(jié)](id:)

---

> 在 select/poll 中,進(jìn)程只有在調(diào)用一定的方法后,內(nèi)核才對(duì)所有監(jiān)視的文件描述符進(jìn)行掃描,而 epoll 事先通過 epoll_ctl() 來注冊一 個(gè)文件描述符,一旦基于某個(gè)文件描述符就緒時(shí),內(nèi)核會(huì)采用類似 callback 的回調(diào)機(jī)制,迅速激活這個(gè)文件描述符,當(dāng)進(jìn)程調(diào)用 epoll_wait() 時(shí)便得到通知。**(此處去掉了遍歷文件描述符,而是通過監(jiān)聽回調(diào)的的機(jī)制。這正是epoll的魅力所在。)**

> epoll的優(yōu)點(diǎn)主要是一下幾個(gè)方面:

>

> * **監(jiān)視的描述符數(shù)量不受限制,它所支持的 FD 上限是最大可以打開文件的數(shù)目**,這個(gè)數(shù)字一般遠(yuǎn)大于2048,舉個(gè)例子,在 1GB 內(nèi)存的機(jī)器上大約是10萬左 右,具體數(shù)目可以 cat /proc/sys/fs/file-max 察看,一般來說這個(gè)數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大。select 的最大缺點(diǎn)就是進(jìn)程打開的 fd 是有數(shù)量限制的。這對(duì) 于連接數(shù)量比較大的服務(wù)器來說根本不能滿足。雖然也可以選擇多進(jìn)程的解決方案(Apache 就是這樣實(shí)現(xiàn)的),不過雖然 linux 上面創(chuàng)建進(jìn)程的代價(jià)比較小,但仍舊是不可忽視的,加上進(jìn)程間數(shù)據(jù)同步遠(yuǎn)比不上線程間同步的高效,所以也不是一種完美的方案。

>

> * **IO 的效率不會(huì)隨著監(jiān)視 fd 的數(shù)量的增長而下降**。epoll 不同于 select 和 poll 輪詢的方式,而是通過每個(gè) fd 定義的回調(diào)函數(shù)來實(shí)現(xiàn)的。只有就緒的 fd 才會(huì)執(zhí)行回調(diào)函數(shù)。

>

> * `如果沒有大量的 idle -connection 或者 dead-connection,epoll 的效率并不會(huì)比 select/poll 高很多,但是當(dāng)遇到大量的 idle- connection,就會(huì)發(fā)現(xiàn) epoll 的效率大大高于 select/poll。`

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容