epoll - 基本使用和细节


781
3 mins.

1.1 epoll 默认设置

  • epoll 默认处于水平触发

1.2 epoll Events

参考 epoll.h [1]

事件简介补充
EPOLLIN文件描述符可读。当有数据可读时触发该事件。
EPOLLPRI有紧急数据可读。用于处理带外数据(out-of-band data)或优先级数据。
EPOLLOUT文件描述符可写。当可以写入数据时触发该事件。
EPOLLRDNORM有普通数据可读。类似于 EPOLLIN,用于读取操作。
EPOLLRDBAND有带外数据可读。类似于 EPOLLPRI,用于处理带外数据。
EPOLLWRNORM可写入普通数据。类似于 EPOLLOUT,用于写入操作。
EPOLLWRBAND可写入带外数据。通常情况下不使用此事件。
EPOLLMSG有消息数据可读。通常情况下不使用此事件。
EPOLLERR文件描述符发生错误。当文件描述符发生错误时触发该事件。
EPOLLHUP文件描述符挂起。当文件描述符的连接关闭时触发该事件。
EPOLLRDHUP文件描述符被远程关闭连接或半关闭连接。在较新的内核版本中使用,旧版本使用 EPOLLHUP 来表示。
EPOLLEXCLUSIVE独占模式。用于实现边缘触发(Edge Triggered)。
EPOLLWAKEUP唤醒等待的线程。用于唤醒被 epoll_wait 阻塞的线程。
EPOLLONESHOT单次事件监听。事件触发后需要重新添加到 epoll 集合中。
EPOLLET边缘触发模式。边缘触发模式仅在文件描述符状态变化时通知一次,与水平触发模式不同。

warning

EPOLLEXCLUSIVE: 可以用于缓解多进程共享的 socket 惊群问题。但是同一进程两次 epoll_wait 之间,如果有新的连接到来并被其他进程处理,当前进程仍旧会被唤醒。[2] epoll_image_1

1.3 epoll 相关函数

epoll 在 kernel-6.4.1 中有 6 个函数,如下所示

1
2
3
4
5
6
7
8
extern int epoll_create (int __size) __THROW;
extern int epoll_create1 () // 相比 epoll_create,增加了flags参数
extern int epoll_ctl (int __epfd, int __op, int __fd,
struct epoll_event *__event) __THROW;
extern int epoll_wait (int __epfd, struct epoll_event *__events,
int __maxevents, int __timeout)
extern int epoll_pwait () // 可以设置阻塞过程中忽略的信号,防止被打断
extern int epoll_pwait2 () // 相比 epoll_wait 增加了超时控制

1.3.1 基本操作

1.3.1.1 创建一个 epoll 实例:

1
2
3
4
5
6
7
// #include <sys/epoll.h>

int epollfd = epoll_create1(0);
if (epollfd == -1) { // 返回 -1 代表失败, 这里必须检查
perror("epoll_create1");
return EXIT_FAILURE;
}

1.3.1.2 epoll_ctl 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// #include <sys/socket.h>
// #include <sys/types.h>

#define DEFAULT_PORT 8080
#define MAX_EVENTS 10

int ret = 0;
// ========= 创建 servfd =========
int servfd = socket(AF_INET /* IPv4 */, SOCK_STREAM, 0);
if (servfd == -1) {
// check error msg by errno
perror("socket error");
return -1
}

struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(DEFAULT_PORT);

ret = bind(servfd, (struct sockaddr*) &servaddr, sizeof(servaddr));
if (ret == -1) {
// check error msg by errno
perror("bind error");
return -1;
}

/**
* Linux2.2之前, 1024 是未完成链接队列最大长度,
* Linux2.2之后,表示完成连接等待应用处理的队列长度,如果需要制定等待连接的队列长度,请使用 tcp_max_syn_backlog (man 7 tcp 可以查看细节)
*/
ret = listen(servfd, 1024);
if (ret == -1) {
// check error msg by errno
perror("listen error");
return -1
}
// ========= 创建 servfd 结束 =========
/**
* struct epoll_event
* {
* uint32_t events; // Epoll events
* epoll_data_t data; // User data variable
* } __EPOLL_PACKED;
**/
struct epoll_event ev;
struct epoll_event events[MAX_EVENTS];

ev.event = EPOLLIN; // set read event
ev.data.fd = servfd;

for (;;) {
int nready = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nready == -1) {
perror("epoll_wait error");
return -1;
}

for (int i = 0; i < nready; i++) {
if (events[i].data.fd == servfd) {
int connfd = accept(servfd, (struct sockaddr*) &cliaddr, &addrlen);
if (connfd == -1) {
perror("accept error");
return -1;
}

setnonblocking(connfd);
// 设置 读事件监听、ET模式
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = connfd;
// 这里添加客户端的 fd 到epoll中
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &ev) == -1) {
perror("epoll_ctl: EPOLL_CTL_ADD error");
return -1;
}
} else {
// pass
// 这里处理客户端sock的事件
}
}
}

1.4 常见问题

挖坑

惊群效应、C10K、连接丢失

1.5 引用

您正在使用不支持或禁用了 JavaScript 的浏览器访问我们的网站。

请考虑启用 JavaScript 以获得更好的浏览体验。

要访问真正的网站 "uocat.com",请手动复制以下网址并在您喜欢的浏览器中打开:

https://uocat.com