epoll_create
#include <sys/epoll.h>
int epoll_create(int size);
返回值:
success:返回一個非0 的未使用過的最小的文件描述符
error:-1 errno被設(shè)置
參數(shù) size 從 Linux 2.6.8 以后就不再使用杖刷,但是必須設(shè)置一個大于 0 的值。
需要注意的是宛官,當(dāng)創(chuàng)建好epoll句柄后媒咳,它就是會占用一個fd值迹缀,在linux下查看/proc/進程id/fd/,是能夠看到這個fd的( eg: ls /proc/$(ps -aux | grep './main' | awk 'NR==1 { print $2 }')/fd
)蜜徽,所以在使用完 epoll 后祝懂,必須調(diào)用close()
關(guān)閉,否則可能導(dǎo)致 fd 被耗盡拘鞋。
- epoll_create1
int epoll_create1(int flags); - flags: - 如果這個參數(shù)是0砚蓬,這個函數(shù)等價于epoll_create(0) - EPOLL_CLOEXEC:這是這個參數(shù)唯一的有效值,如果這個參數(shù)設(shè)置為這個盆色。 那么當(dāng)進程替換映像的時候會關(guān)閉這個文件描述符灰蛙,這樣新的映像中就無法對這個文件描述符操作, 適用于多進程編程+映像替換的環(huán)境里
epoll_ctl
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注冊函數(shù)
第一個參數(shù)是 epoll_create() 的創(chuàng)建的 epoll 實例隔躲。
第二個參數(shù)表示動作摩梧,用3個宏表示:
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)如下:
#include <sys/epoll.h>
struct epoll_event {
__uint32_t events;
epoll_data_t data;
}
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
- 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)來說的
epoll_wait
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout);
等待事件的產(chǎn)生仅父。
參數(shù) events 用來從內(nèi)核得到事件的集合。
參數(shù) maxevents 告之內(nèi)核這個events有多大(數(shù)組成員的個數(shù))浑吟。
參數(shù) timeout 是超時時間笙纤,單位毫秒(0會立即返回,-1將是永久阻塞)组力。
該函數(shù)返回需要處理的事件數(shù)目省容,返回的事件集合在 events 數(shù)組中,如返回 0 表示已超時燎字。
- maxevents 參數(shù)設(shè)置多少合適腥椒?
muduo 中的思路是動態(tài)擴張:開始是 n 個阿宅,當(dāng)發(fā)現(xiàn)有事件的 fd 數(shù)量已經(jīng)到達 n 個后,將 struct epoll_event 數(shù)量調(diào)整成 2n 個寞酿,下次如果還不夠家夺,則變成 4n 個,以此類推伐弹。// 初始化代碼 std::vector<struct epoll_event> events_(16); // 線程循環(huán)里面的代碼 while (m_bExit) { int numEvents = ::epoll_wait(epollfd_, &*events_.begin(), static_cast<int>(events_.size()), 1); if (numEvents > 0) { if (static_cast<size_t>(numEvents) == events_.size()) { events_.resize(events_.size() * 2); } } }
- timeout 超時時間設(shè)置多少合適拉馋?
muduo 中epoll_wait 的超時事件設(shè)置為 1 毫秒。
代碼示例
服務(wù)器
#include "lib/common.h"
#define MAXEVENTS 128
char rot13_char(char c)
{
if ((c >= 'a' && c <= 'm') || (c >= 'A' && c <= 'M'))
return c + 13;
else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))
return c - 13;
else
return c;
}
int main(int argc, char **argv)
{
int listen_fd, socket_fd;
int n, i;
int efd;
struct epoll_event event;
struct epoll_event *events;
listen_fd = tcp_nonblocking_server_listen(SERV_PORT);
efd = epoll_create1(0);
if (efd == -1) {
error(1, errno, "epoll create failed");
}
event.data.fd = listen_fd;
event.events = EPOLLIN | EPOLLET;
if (epoll_ctl(efd, EPOLL_CTL_ADD, listen_fd, &event) == -1) {
error(1, errno, "epoll_ctl add listen fd failed");
}
/* Buffer where events are returned */
events = calloc(MAXEVENTS, sizeof(event));
while (1) {
n = epoll_wait(efd, events, MAXEVENTS, -1);
printf("epoll_wait wakeup\n");
for (i = 0; i < n; i++) {
if ((events[i].events & EPOLLERR) ||
(events[i].events & EPOLLHUP) ||
(!(events[i].events & EPOLLIN))) {
fprintf(stderr, "epoll error\n");
close(events[i].data.fd);
continue;
} else if (listen_fd == events[i].data.fd) {
struct sockaddr_storage ss;
socklen_t slen = sizeof(ss);
int fd = accept(listen_fd, (struct sockaddr *) &ss, &slen);
if (fd < 0) {
error(1, errno, "accept failed");
} else {
make_nonblocking(fd);
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET; //edge-triggered
if (epoll_ctl(efd, EPOLL_CTL_ADD, fd, &event) == -1) {
error(1, errno, "epoll_ctl add connection fd failed");
}
}
continue;
} else {
socket_fd = events[i].data.fd;
printf("get event on socket fd == %d \n", socket_fd);
while (1) {
char buf[512];
if ((n = read(socket_fd, buf, sizeof(buf))) < 0) {
if (errno != EAGAIN) {
error(1, errno, "read error");
close(socket_fd);
}
break;
} else if (n == 0) {
close(socket_fd);
break;
} else {
for (i = 0; i < n; ++i) {
buf[i] = rot13_char(buf[i]);
}
if (write(socket_fd, buf, n) < 0) {
error(1, errno, "write error");
}
}
}
}
}
}
free(events);
close(listen_fd);
}
LT和ET 工作模式
epoll對文件描述符的操作有2種模式:LT和ET惨好。
LT模式(水平觸發(fā)):
LT(水平觸發(fā))是默認的工作模式煌茴,并且同時支持阻塞和非阻塞socket。
對于采用LT工作模式的文件描述符日川,當(dāng)epoll_wait檢測到其上有時間發(fā)生并將此事件通知應(yīng)用程序后蔓腐,應(yīng)用程序可以不立即處理該事件,這樣龄句,當(dāng)應(yīng)用程序下一次調(diào)用epoll_wait時回论,epoll_wait還會再次相應(yīng)應(yīng)用程序通告此事件,直到該事件被處理分歇。-
ET模式(邊沿觸發(fā)):
ET是一種高效的模式傀蓉,只支持非阻塞socket。
對于采用ET模式的文件描述符职抡,當(dāng)epoll_wait檢測到其上有事件發(fā)生并將此時間通知應(yīng)用程序以后葬燎,應(yīng)用程序可以不立即處理該事件,但是后續(xù)的epoll_wait將不再向應(yīng)用程序通知這一事件缚甩。- 為什么將ET稱為高效的工作方式了谱净?
因為ET不用多次觸發(fā),減少了每次epoll_wait可能需要返回的fd數(shù)量擅威,在并發(fā)event數(shù)量極多的情況下能夠加快epoll_wait的處理速度壕探。
注意 epoll 工作在 ET模式的時候,必須使用非阻塞套接口郊丛,以避免由于一個文件句柄的 阻塞讀/阻塞寫 操作把處理多個文件描述符的任務(wù)餓死浩蓉。
- 為什么將ET稱為高效的工作方式了谱净?
如果對于一個非阻塞 socket,如果使用 epoll 邊緣模式去檢測數(shù)據(jù)是否可讀宾袜,觸發(fā)可讀事件以后捻艳,一定要一次性把 socket 上的數(shù)據(jù)收取干凈才行,也就是一定要循環(huán)調(diào)用 recv 函數(shù)直到 recv 出錯庆猫,錯誤碼是 EWOULDBLOCK
或者 EAGAIN
认轨。
如果使用水平模式,則不用月培,你可以根據(jù)業(yè)務(wù)一次性收取固定的字節(jié)數(shù)嘁字,或者收完為止恩急。邊緣模式下收取數(shù)據(jù)的代碼示例如下:
參考資料
[1]《UNIX 網(wǎng)絡(luò)編程》3th [美] W.Richard Stevens,Bill Fenner纪蜒,Andrew M. Rudoff
[2] http://www.cnblogs.com/ajianbeyourself/p/5859989.html
[3] https://blog.csdn.net/hnlyyk/article/details/50946194