關(guān)于 select、poll、epoll 的區(qū)別

1. 函數(shù)說明

1.1 select

/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, 
           fd_set *exceptfds, struct timeval *timeout);

void FD_ZERO(fd_set *set);          //清空集合
void FD_CLR(int fd, fd_set *set);   //將一個給定的文件描述符從集合中刪除
void FD_SET(int fd, fd_set *set);   //將一個給定的文件描述符加入集合之中
int  FD_ISSET(int fd, fd_set *set); //檢查集合中指定的文件描述符是否可以讀寫 

struct timeval {
   long    tv_sec;         /* seconds 秒*/
   long    tv_usec;        /* microseconds 微妙*/
};

該函數(shù)準(zhǔn)許進(jìn)程指示內(nèi)核等待多個事件中的任何一個發(fā)生,并在有一個或多個事件發(fā)生或經(jīng)歷一段指定的時間后才被喚醒。

  • 第一個參數(shù) nfds 指定待測試的描述符個數(shù),其值為最大的文件描述符加1,描述符0、1、2...nfds-1均將被測試(因為文件描述符是從0開始的)。
  • 中間的三個參數(shù) readset、writeset、exceptset 指定我們要讓內(nèi)核測試讀、寫、異常條件的描述符。如果對某一個的條件不感興趣,就可以把它設(shè)為 NULL。struct fd_set 可以理解為一個集合(實際上是一long類型的數(shù)組),這個集合中存放的是文件描述符,可通過上面那四個函數(shù)進(jìn)行設(shè)置。
  • timeout 告知內(nèi)核等待所指定描述符中的任何一個就緒最多等待時長。沒有等到就緒就會超時。該參數(shù)控制三種可能:
    ① 傳 NULL 時,一直阻塞直到有感興趣的描述符上有IO事件就緒。
    ② timeval結(jié)構(gòu)中指定的秒數(shù)和微秒數(shù)都是0,檢查描述符后立即返回,不阻塞。
    ③ timeval結(jié)構(gòu)中指定的秒數(shù)和微秒數(shù)指定了時長,在指定時長超時之前有 IO事件就緒返回,或者超時返回。

1.2 poll

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

struct pollfd {
    int   fd;         /* file descriptor 文件描述符 */
    short events;     /* requested events 等待的事件 */
    short revents;    /* returned events 實際發(fā)生了的事件 */
};

每一個 struct pollfd 結(jié)構(gòu)體指定了一個被監(jiān)視的文件描述符,可以傳遞多個結(jié)構(gòu)體,讓 poll() 監(jiān)視多個文件描述符。每個結(jié)構(gòu)體的 events 域是監(jiān)視該文件描述符的事件掩碼,由用戶來設(shè)置這個域。revents 域是文件描述符的操作結(jié)果事件掩碼,內(nèi)核在調(diào)用返回時設(shè)置這個域。events 域中請求的任何事件都可能在 revents 域中返回。

合法的事件如下:

常量 說明
POLLIN 普通或優(yōu)先級帶數(shù)據(jù)可讀
POLLRDNORM 普通數(shù)據(jù)可讀
POLLRDBAND 優(yōu)先級帶數(shù)據(jù)可讀
POLLPRI 高優(yōu)先級數(shù)據(jù)可讀
POLLOUT 普通數(shù)據(jù)可寫
POLLWRNORM 普通數(shù)據(jù)可寫
POLLWRBAND 優(yōu)先級帶數(shù)據(jù)可寫
POLLERR 發(fā)生錯誤
POLLHUP 發(fā)生掛起
POLLNVAL 描述字不是一個打開的文件

POLLIN | POLLPRI 等價于 select() 的讀事件
POLLOUT 等價于 select() 的寫事件

① timeout 參數(shù)指定等待的毫秒數(shù),在超時之前有 IO 事件就緒或者超時,poll 都會返回
② timeout 為負(fù)數(shù)值,表示一直阻塞直到一個指定事件發(fā)生,poll 才返回。
③ timeout 為0,poll 調(diào)用立即返回并列出準(zhǔn)備好I/O的文件描述符,但并不等待其它的事件。

返回值和錯誤代碼
成功時:poll() 返回結(jié)構(gòu)體中 revents 域不為0的文件描述符個數(shù)。
超時后:如果在超時前沒有任何事件發(fā)生,poll()返回0。
失敗時:poll() 返回-1,并設(shè)置errno為下列值之一

EFAULT    指針指向的地址超出進(jìn)程的地址空間。
EINTR    請求的事件之前產(chǎn)生一個信號,調(diào)用可以重新發(fā)起。
EINVAL    參數(shù)超出 PLIMIT_NOFILE 值。
ENOMEM   可用內(nèi)存不足,無法完成請求。

1.3 epoll

#include <sys/epoll.h>

int epoll_create(int size);
int epoll_create1(int flags);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

typedef union epoll_data {
   void        *ptr;
   int          fd;
   __uint32_t   u32;
   __uint64_t   u64;
} epoll_data_t;

struct epoll_event {
   __uint32_t   events;      /* Epoll events */
   epoll_data_t data;        /* User data variable */
};

(1)創(chuàng)建 epoll 實例
現(xiàn)在一般使用 epoll_create1(EPOLL_CLOEXEC),原因:

  • 在linux 內(nèi)核版本大于2.6.8 后,epoll_create(int size) 這個 size 參數(shù)就被棄用了,但是傳入的值必須大于0。最初實現(xiàn)版本時, size參數(shù)的作用告訴內(nèi)核需要使用多少個文件描述符。內(nèi)核會使用 size 的大小去申請對應(yīng)的內(nèi)存。現(xiàn)在這個size參數(shù)不再使用了,內(nèi)核會動態(tài)的申請需要的內(nèi)存。
  • 使用 epoll_create1() 的優(yōu)點是它允許你指定標(biāo)志,指定 EPOLL_CLOEXEC 標(biāo)記在執(zhí)行另一個進(jìn)程時文件描述符會自動關(guān)閉。

(2)epoll 的事件注冊
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
先注冊要監(jiān)聽的事件類型。
第一個參數(shù)是 epoll_create1 的返回值
第二個參數(shù)表示動作,用三個宏來表示:

EPOLL_CTL_ADD:注冊新的 fd 到 epfd 中
EPOLL_CTL_MOD:修改已經(jīng)注冊的 fd 的監(jiān)聽事件
EPOLL_CTL_DEL :從 epfd 中刪除一個 fd

第三個參數(shù)是需要監(jiān)聽的 fd
第四個參數(shù)是告訴內(nèi)核需要監(jiān)聽什么事,struct epoll_event 結(jié)構(gòu)見上面代碼
events 可以是以下幾個宏的集合:

常量 說明
EPOLLIN 表示對應(yīng)的文件描述符可以讀(包括對端SOCKET正常關(guān)閉)
EPOLLOUT 表示對應(yīng)的文件描述符可以寫
EPOLLPRI 表示對應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有帶外數(shù)據(jù)到來)
EPOLLERR 表示對應(yīng)的文件描述符發(fā)生錯誤
EPOLLHUP 表示對應(yīng)的文件描述符被掛斷
EPOLLET 將 EPOLL 設(shè)為邊緣觸發(fā) (Edge Triggered) 模式,這是相對于水平觸發(fā) (Level Triggered) 來說的,LT是缺省的工作方式
EPOLLONESHOT 只監(jiān)聽一次事件,當(dāng)監(jiān)聽完這次事件之后,如果還需要繼續(xù)監(jiān)聽這個socket的話,需要再次把這個 socket 加入到 EPOLL 隊列里

LT (Level Triggered) 水平觸發(fā):默認(rèn)的模式。內(nèi)核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的 fd 進(jìn)行 IO 操作。如果你不作任何操作,內(nèi)核還是會繼續(xù)通知你。

ET (Edge Triggered) 邊緣觸發(fā):“高速”模式。內(nèi)核只會提示一次,直到下次再有數(shù)據(jù)流入之前都不會再提示了,無論 fd 中是否還有數(shù)據(jù)可讀。在ET模式下,read一個fd的時候一定要把數(shù)據(jù)讀完。

(3)等待事件的產(chǎn)生
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
等待事件的產(chǎn)生,類似于 select() 調(diào)用。
參數(shù) events 用來從內(nèi)核得到事件的集合
參數(shù) maxevents 告之內(nèi)核這個 events 有多大
參數(shù) timeout 是超時時間(毫秒,0會立即返回,-1一直阻塞直到有IO事件就緒)
該函數(shù)返回需要處理的事件數(shù)目,如返回0表示已超時。

2. 三者之間的關(guān)聯(lián)和區(qū)別

關(guān)聯(lián)

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

區(qū)別

關(guān)于最大連接數(shù)先理清楚2個概念:
⑴ 一個進(jìn)程能打開的最大文件描述符,使用 ulimit -n 或者 cat /proc/進(jìn)程號/limits |grep "Max open file"
? 使用 ulimit -n xxx 修改(只針對當(dāng)前session有效),
? 通過 setrlimit 系統(tǒng)調(diào)用修改(只對當(dāng)前進(jìn)程有效),
? 修改 /etc/security/limits.conf 在該文件中添加以下兩行:

    *      soft    nofile     100000
    *      hard    nofile     100000

這個值是可以修改的,但是最大不要超過系統(tǒng)所能打開的最大數(shù),超過了也沒有什么意義。

⑵ 一個系統(tǒng)所能打開的最大數(shù)也是有限的,跟內(nèi)存大小有關(guān),可以通過cat /proc/sys/fs/file-max 查看。

  • 注意:對于服務(wù)器程序來說并不是“理論”最大只能打開65535個 socket 連接。65535 只是linux系統(tǒng)的最大可用socket端口,比如在一臺服務(wù)器(這里指主機而非服務(wù)程序)上“理論”上最多只能發(fā)起65535個客戶端連接(實際要小于)這是對的,因為每發(fā)起一個連接都需要一個端口。服務(wù)器程序只需監(jiān)聽幾個端口就夠了,它的上限是系統(tǒng)所能打開的最大文件描述符的數(shù)量。不要搞混了這兩個概念。
> 支持一個進(jìn)程所能打開的最大連接數(shù)
  • select 最大連接數(shù)有一定限制的,由 FD_SETSIZE 值決定的,默認(rèn)值是2048。可以對進(jìn)行修改,需要重新編譯內(nèi)核,但是性能可能會受到影響。
  • poll 最大連接數(shù)上限是系統(tǒng)能最大可以打開文件的數(shù)目,一般來說這個數(shù)目受限于系統(tǒng)內(nèi)存,1GB內(nèi)存的機器上大約是10萬左右,具體數(shù)目可以 cat /proc/sys/fs/file-max。
  • epoll 與 poll 一樣,最大連接數(shù)上限是系統(tǒng)能最大可以打開文件的數(shù)目。
> 連接數(shù)劇增后帶來的 IO 效率問題
  • select 每次調(diào)用都對所有的連接進(jìn)行線性遍歷,所以隨著連接數(shù)的增加會造成遍歷速度的線性下降的性能問題。
  • poll 與 select 有相同的問題
  • epoll 是事件驅(qū)動的,內(nèi)核中的實現(xiàn)是根據(jù)每個連接 fd 上的 callback 函數(shù)來實現(xiàn)的,只有活躍的 socket 才會主動調(diào)用 callback,所以在活躍 socket 較少的情況下,使用 epoll 沒有前面兩者的線性下降的性能問題,但是所有 socket 都很活躍的情況下,可能也會有性能問題。

epoll 如何實現(xiàn)只處理活躍連接
epoll實現(xiàn)了eventpoll數(shù)據(jù)結(jié)構(gòu)
數(shù)據(jù)結(jié)構(gòu)中rdlist將活躍連接存儲在鏈表中,當(dāng)網(wǎng)卡發(fā)送報文時,增加節(jié)點,當(dāng)讀取一個事件后,鏈表刪除節(jié)點,需要得到活躍連接就只需要遍歷鏈表
數(shù)據(jù)結(jié)構(gòu)中rdr使用紅黑樹(自平衡二叉樹)將事件存儲,例如:當(dāng)有讀事件時,就新增節(jié)點,事件復(fù)雜度為logN

一般來說編寫高并發(fā)服務(wù)器程序都會首先 epoll 因為這種環(huán)境下其性能最好,但是在連接數(shù)少并且連接都十分活躍的情況下,select 和 poll 的性能可能比 epoll 好,畢竟 epoll 的通知機制需要很多函數(shù)回調(diào)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內(nèi)容