epoll_event的概念和使用案例
epoll_event
是 Linux 下 epoll
I/O 多路复用机制的核心数据结构,用于描述文件描述符(File Descriptor, FD)上发生的事件及其关联的用户数据。通过 epoll
,可以高效地监控多个文件描述符的状态变化(如可读、可写、错误等)。
epoll_event
结构定义
#include <sys/epoll.h>
struct epoll_event {
uint32_t events; // 需要监听的事件类型(bitmask)
epoll_data_t data; // 用户数据,通常包含文件描述符
};
typedef union epoll_data {
void* ptr;
int fd; // 通常关联的 FD
uint32_t u32;
uint64_t u64;
} epoll_data_t;
-
events
:表示关注的事件类型,常用值:EPOLLIN
:文件描述符可读(如 socket 接收到数据)。EPOLLOUT
:文件描述符可写(如 socket 可以发送数据)。EPOLLERR
:发生错误。EPOLLHUP
:对端关闭连接。EPOLLET
:设置为边缘触发(Edge-Triggered)模式(默认是水平触发 Level-Triggered)。
-
data
:用户数据联合体,通常用fd
字段保存关联的文件描述符。
使用步骤
- 创建
epoll
实例:epoll_create1()
。 - 注册/修改事件:
epoll_ctl()
添加(EPOLL_CTL_ADD
)、修改(EPOLL_CTL_MOD
)或删除(EPOLL_CTL_DEL
)事件。 - 等待事件:
epoll_wait()
阻塞等待事件发生。 - 处理事件:遍历就绪的事件并处理。
示例代码:TCP 服务器监控连接和数据
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#define MAX_EVENTS 10
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_fd, client_fd, epoll_fd;
struct sockaddr_in addr;
struct epoll_event event, events[MAX_EVENTS];
char buffer[BUFFER_SIZE];
// 1. 创建 TCP 服务器 socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(PORT);
bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));
listen(server_fd, 5);
// 2. 创建 epoll 实例
epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
// 3. 注册服务器 socket 到 epoll,监听可读事件(新连接)
event.events = EPOLLIN;
event.data.fd = server_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event);
printf("Server listening on port %d...\n", PORT);
while (1) {
// 4. 等待事件发生(阻塞调用)
int n_ready = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (n_ready == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
// 5. 处理所有就绪事件
for (int i = 0; i < n_ready; i++) {
int current_fd = events[i].data.fd;
// 服务器 socket 可读:新连接到达
if (current_fd == server_fd) {
client_fd = accept(server_fd, NULL, NULL);
if (client_fd == -1) {
perror("accept");
continue;
}
// 将新连接的客户端 socket 加入 epoll 监听
event.events = EPOLLIN;
event.data.fd = client_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event);
printf("New client connected: fd=%d\n", client_fd);
}
// 客户端 socket 可读:接收数据
else if (events[i].events & EPOLLIN) {
ssize_t bytes_read = read(current_fd, buffer, BUFFER_SIZE);
if (bytes_read <= 0) {
// 连接关闭或错误,移除监听并关闭 socket
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, current_fd, NULL);
close(current_fd);
printf("Client fd=%d disconnected.\n", current_fd);
} else {
buffer[bytes_read] = '\0';
printf("Received from fd=%d: %s\n", current_fd, buffer);
// 回显数据(示例)
write(current_fd, buffer, bytes_read);
}
}
}
}
close(server_fd);
return 0;
}
关键解释
- 服务器初始化:创建 TCP 服务器 socket 并绑定端口。
- 注册服务器 socket:将服务器 socket 加入
epoll
监听列表,关注EPOLLIN
事件(新连接到达)。 - 事件循环:
epoll_wait()
返回所有就绪的事件。- 如果是服务器 socket 就绪,调用
accept()
接受新连接,并将新客户端 socket 加入epoll
。 - 如果是客户端 socket 可读,读取数据并处理;若读取失败(如连接关闭),则移除监听并关闭 socket。
触发模式
- 水平触发(LT,默认):只要文件描述符处于就绪状态,
epoll_wait()
会持续报告事件。 - 边缘触发(ET):仅在状态变化时报告一次事件。需搭配非阻塞 IO,并循环读取数据直到
EAGAIN
错误。
设置 ET 模式示例:
event.events = EPOLLIN | EPOLLET; // 边缘触发
通过 epoll_event
,可以高效管理成千上万的并发连接,是高性能网络服务器的核心机制(如 Nginx、Redis)。