17.1 epoll 理解及应用
2026/1/8大约 6 分钟
17.1 epoll 理解及应用
基于select 的 I/O 复用技术速度慢的原因
使用select的服务端代码:
while(1)
{
cp_reads = reads;
timeout.tv_sec = 5;
timeout.tv_usec = 5000;
fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout);
if(fd_num == -1)
break;
else if(fd_num == 0)
continue;
for(i = 0; i < fd_max + 1; i++)
{
if(FD_ISSET(i, &cpy_reads))
{
//...
}
}
}| 行号 | 功能 | 说明 |
|---|---|---|
| 3-5 | 重置监视对象 | 每次都需要重新向select 传递监视对象信息 |
| 11 | 遍历整个监视对象列表 | 因为select 的结果保存在监视信息列表中,它没有将发生了变化的部分单独返回,而是要对所有监视对象逐个进行判断 |
| 造成性能无法提升的原因: |
- 套接字由操作系统管理
- 监视socket 的变化要由操作系统完成
- 程序向操作系统传递数据 - 监视的socket对象列表 会很慢,且传递的过程无法通过优化代码完成(起码是无法通过用户层级的代码)
- select 每次都要向操作系统传递监视对象
由此改善的地方就是:
- 只在最开始向操作系统传递监视对象,后面如果有变化再重新传递,没有变化时不用传递。
- 返回发生变换的对象,而不是遍历所有对象
相关信息
仅向操作系统传递1次监视对象,监视范围或内容发生变化时只通知发生变化的事项。
不同平台有不同的实现:
- Linux:epoll
- Windows:IOCP
- BSD:kqueue
select的优点
- 大部分系统都支持select:代码兼容
- 在接入端较少:不需要考虑性能瓶颈,或epoll 没有较大提升
实现epoll时必要的函数和结构体
- create: 创建监听对象的集合
- ctl:从集合中添加、删除对象,或 修改监听的事件
- wait:等待监听对象发生变化
- epoll_event: 事件
相关信息
epoll方式下由操作系统负责保存监视对象文件描述符
epoll_create
NAME
epoll_create, epoll_create1 - open an epoll file descriptor
SYNOPSIS
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_create1(int flags);DESCRIPTION
epoll_create() creates an epoll(7) instance. Since Linux 2.6.8, the size argument is ignored, but must be greater than zero; see NOTES below.
epoll_create() returns a file descriptor referring to the new epoll instance. This file descriptor is used for all the subsequent calls to the epoll interface. When no longer required, the file descriptor returned by
epoll_create() should be closed by using close(2). When all file descriptors referring to an epoll instance have been closed, the kernel destroys the instance and releases the associated resources for reuse.- 参数size 没有作用:内核会根据情况调整epoll例程-保存文件描述符的空间 的大小
- epoll_create 返回的文件描述符在不使用时也应该关闭,以便内核释放相关资源
epoll_ctl
NAME
epoll_ctl - control interface for an epoll descriptor
SYNOPSIS
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);| 参数 | 值 | 用途 | 说明 |
|---|---|---|---|
| epfd | the epoll instance referred to by the file descriptor epfd | 通过epoll_create 创建的epoll instance | |
| op | EPOLL_CTL_ADD EPOLL_CTL_MOD EPOLL_CTL_DEL | ADD:Register the target file descriptor fd on the epoll instance referred to by the file descriptor epfd and associate the event event with the internal file linked to fd.MOD:Change the event event associated with the target file descriptor fd.DEL:Remove (deregister) the target file descriptor fd from the epoll instance referred to by epfd. The event is ignored and can be NULL | 在添加监视的fd时,可以设置关联的event |
| fd | |||
| event | describes the object linked to the file descriptor fd. | 要监视的对象的事件:可读、可写。。。 |
events
| 值 | 含义 |
|---|---|
| EPOLLIN | The associated file is available for read(2) operations. |
| EPOLLOUT | The associated file is available for write(2) operations. |
| EPOLLERR | Error condition happened on the associated file descriptor. epoll_wait(2) will always wait for this event; it is not necessary to set it in events. |
| 。。。 |
源代码:
enum EPOLL_EVENTS
{
EPOLLIN = 0x001,
#define EPOLLIN EPOLLIN
EPOLLPRI = 0x002,
#define EPOLLPRI EPOLLPRI
EPOLLOUT = 0x004,
#define EPOLLOUT EPOLLOUT
//...
EPOLLONESHOT = 1u << 30,
#define EPOLLONESHOT EPOLLONESHOT
EPOLLET = 1u << 31
#define EPOLLET EPOLLET
};epoll_data
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;示例:
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);问:在add 或 mod的时候已经提供了fd,还需要设置event中的fd吗?
答:用来在wait返回时判断event对应的fd(是哪个fd产生的)
epoll_wait
NAME
epoll_wait, epoll_pwait - wait for an I/O event on an epoll file descriptor
SYNOPSIS
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);| 参数 | 值 | 用途 | 说明 |
|---|---|---|---|
| epfd | |||
| events | 书中说明需要“动态分配”,man 中未说明。chatgpt回答只要是连续、有效、可写的内存即可 | ||
| maxevents | 最大的事件数量 | ||
| timeout | specifies the number of milliseconds that epoll_wait() will block. The call will block until either: - a file descriptor delivers an event; - the call is interrupted by a signal handler; or - the timeout expires. | 传递-1时epoll_wait 一直等待直到其余两种情况发生 | |
| RETURN | When successful, epoll_wait() returns the number of file descriptors ready for the requested I/O, or zero if no file descriptor became ready during the requested timeout milliseconds. When an error occurs, epoll_wait() returns -1 and errno is set appropriately. |
示例代码
#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;
/* Code to set up listening socket, 'listen_sock',
(socket(), bind(), listen()) omitted */
epollfd = epoll_create1(0);
if (epollfd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
perror("epoll_ctl: listen_sock");
exit(EXIT_FAILURE);
}
for (;;) {
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
for (n = 0; n < nfds; ++n) {
if (events[n].data.fd == listen_sock) {
conn_sock = accept(listen_sock,
(struct sockaddr *) &local, &addrlen);
if (conn_sock == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
setnonblocking(conn_sock);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
&ev) == -1) {
perror("epoll_ctl: conn_sock");
exit(EXIT_FAILURE);
}
} else {
do_use_fd(events[n].data.fd);
}
}
}| 行号 | 功能 | 说明 |
|---|---|---|
| 8 | 创建epoll | 这里使用的create1(0) 或者 create(1) |
| 14-19 | 监视 listen socket 的可读事件(有请求连接) | |
| 22 | 等待事件发生 | |
| 28 | 遍历事件列表 | |
| 29 | 判断event 是否为listen socket 所产生的 | |
| 30 | 接收连接请求,建立新的连接 | |
| 37-43 | 将与客户端连接的socket 添加到 epoll中,监视其read、write | |
| 44-46 | 对于非listen socket,即 ESTABLISHED 状态 的socket 处理数据通讯 |
**对比select 和 epoll **
| 内容 | select | epoll |
|---|---|---|
| 发生变化的fd | fd_set中bit位的序号 和 fd对应,通过循环遍历 + FD_ISSET逐位判断 | 在add或mod时,将event中的data设置为fd值,wait 返回时判断fd值是否相同 |
| 要监视的事件 | select有不同的监视集合参数:readfds、writefds、exceptfds | events 可组合多种事件 |
| 具体发生的事件的类型 | 对应集合中FD_ISSET返回真的就是处于该状态 | 通过event的值? |
| 支持的fd类型 | regular file、directory、char dev、block dev、pipe、FIFO、socket select 通过查询fd的状态 | pipe、FIFO、socket、eventfd、timerfd、signalfd、inotify fd epoll 通过fd产生事件,通过事件获取状态。regular file 、directory不会产生事件,也就不能用于epoll |
问:对于listen 状态的socket,其可读表示有新的连接请求,那么其是否可写?如果可写代表着什
么?
这里说是可读含义感觉不是很清楚,有数据到达接收缓冲-可以读取数据?
测试
在示例代码的基础上增加打印
ming@ubuntu:/media/sf_share/Network/build$ ./Network 10086
Socket created, listen_sock fd:3
Number of events: 1
n:0 event:1, fd=3
Connection established with client, client fd:5, ip:192.168.56.1, port:2372
Number of events: 1
n:0 event:1, fd=5
Received message: abc
Number of events: 1
n:0 event:1, fd=3
Connection established with client, client fd:6, ip:192.168.56.1, port:2373
Number of events: 1
n:0 event:1, fd=6
Received message: 123| 行号 | 功能 | 说明 |
|---|---|---|