select和poll都只提供了一個函數(shù):select或者poll函數(shù)。
而epoll提供了三個函數(shù),epoll_create,epoll_ctl和epoll_wait,epoll_create是創(chuàng)建一個epoll句柄;epoll_ctl是注冊要監(jiān)聽的事件類型;epoll_wait則是等待事件的產(chǎn)生。
epoll除了提供select/poll那種IO事件的水平觸發(fā)(Level Triggered)外,還提供了邊緣觸發(fā)(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態(tài),減少epoll_wait/epoll_pwait的調(diào)用,提高應(yīng)用程序效率。
水平觸發(fā)(level-trggered)
只要文件描述符關(guān)聯(lián)的讀內(nèi)核緩沖區(qū)非空,有數(shù)據(jù)可以讀取,就一直發(fā)出可讀信號進行通知,
當文件描述符關(guān)聯(lián)的內(nèi)核寫緩沖區(qū)不滿,有空間可以寫入,就一直發(fā)出可寫信號進行通知
LT模式支持阻塞和非阻塞兩種方式。epoll默認的模式是LT。
邊緣觸發(fā)(edge-triggered)
當文件描述符關(guān)聯(lián)的讀內(nèi)核緩沖區(qū)由空轉(zhuǎn)化為非空的時候,則發(fā)出可讀信號進行通知,
當文件描述符關(guān)聯(lián)的內(nèi)核寫緩沖區(qū)由滿轉(zhuǎn)化為不滿的時候,則發(fā)出可寫信號進行通知
兩者的區(qū)別在哪里呢?水平觸發(fā)是只要讀緩沖區(qū)有數(shù)據(jù),就會一直觸發(fā)可讀信號,而邊緣觸發(fā)僅僅在空變?yōu)榉强盏臅r候通知一次,
LT(level triggered)是缺省的工作方式,并且同時支持block和no-block socket.在這種做法中,內(nèi)核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的fd進行IO操作。如果你不作任何操作,內(nèi)核還是會繼續(xù)通知你的,所以,這種模式編程出錯誤可能性要小一點。傳統(tǒng)的select/poll都是這種模型的代表.
水平觸發(fā)和邊緣觸發(fā)模式區(qū)別
- 讀緩沖區(qū)剛開始是空的
- 讀緩沖區(qū)寫入2KB數(shù)據(jù)
- 水平觸發(fā)和邊緣觸發(fā)模式此時都會發(fā)出可讀信號
- 收到信號通知后,讀取了1kb的數(shù)據(jù),讀緩沖區(qū)還剩余1KB數(shù)據(jù)
- 水平觸發(fā)會再次進行通知,而邊緣觸發(fā)不會再進行通知
所以,邊緣觸發(fā)需要一次性的把緩沖區(qū)的數(shù)據(jù)讀完為止,也就是一直讀,直到讀到EGAIN為止,EGAIN說明緩沖區(qū)已經(jīng)空了,因為這一點,邊緣觸發(fā)需要設(shè)置文件句柄為非阻塞
//水平觸發(fā)
ret = read(fd, buf, sizeof(buf));
//邊緣觸發(fā)
while(true) {
ret = read(fd, buf, sizeof(buf);
if (ret == EAGAIN) break;
}
設(shè)置方法
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注冊函數(shù),它不同與select()是在監(jiān)聽事件時告訴內(nèi)核要監(jiān)聽什么類型的事件,而是在這里先注冊要監(jiān)聽的事件類型。
第一個參數(shù)是epoll_create()的返回值,
第二個參數(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)如下:
struct epoll_event {
__uint32_t events; /* Epoll events /
epoll_data_t data; / User data variable */
};
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)來說的。
EPOLLONESHOT:只監(jiān)聽一次事件,當監(jiān)聽完這次事件之后,如果還需要繼續(xù)監(jiān)聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里。
使用示例
使用ET的例子:nginx
使用LT的例子:redis