> 技术文档 > epoll_event数据结构及使用案例详解

epoll_event数据结构及使用案例详解


epoll_event 数据结构详解

在 Linux 的 I/O 多路复用机制 epoll 中,epoll_event 是关键的数据结构,用于描述文件描述符(fd)上的事件和关联数据。其定义在头文件 中:

struct epoll_event { uint32_t events; // 事件掩码(位图) epoll_data_t data; // 用户数据(联合体)};typedef union epoll_data { void *ptr; // 自定义指针(常用) int fd; // 关联的文件描述符(常用) uint32_t u32; // 32位整数 uint64_t u64; // 64位整数} epoll_data_t;
核心字段解析:
  1. events (事件标志位)

    • EPOLLIN:fd 可读(有数据到达)
    • EPOLLOUT:fd 可写(可发送数据)
    • EPOLLERR:fd 发生错误
    • EPOLLHUP:fd 被挂断(对端关闭连接)
    • EPOLLET:启用边缘触发模式(默认水平触发)
    • EPOLLRDHUP:流套接字对端关闭连接(或半关闭)
  2. data (用户数据联合体)

    • 常用 fdptr 存储与事件相关的自定义数据(如连接上下文)

使用案例:基于 ET 模式的 Echo 服务器

以下示例展示 epoll_event 在非阻塞 TCP 服务器中的典型用法(边缘触发模式):

#include #include #include #include #include #include #include #define MAX_EVENTS 64#define PORT 8080// 设置 fd 为非阻塞模式void set_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK);}int main() { int server_fd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(PORT), .sin_addr.s_addr = INADDR_ANY }; // 绑定并监听 bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)); listen(server_fd, SOMAXCONN); // 创建 epoll 实例 int epoll_fd = epoll_create1(0); struct epoll_event ev, events[MAX_EVENTS]; // 添加 server_fd 到 epoll ev.events = EPOLLIN | EPOLLET; // 边缘触发模式 ev.data.fd = server_fd; // 存储文件描述符 epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev); while (1) { int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); for (int i = 0; i < nfds; i++) { // 处理新连接 if (events[i].data.fd == server_fd) { struct sockaddr_in client_addr; socklen_t len = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &len); set_nonblocking(client_fd); // 注册客户端 fd ev.events = EPOLLIN | EPOLLET | EPOLLRDHUP; ev.data.fd = client_fd; // 存储客户端 fd epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev); } // 处理客户端数据 else if (events[i].events & EPOLLIN) { int client_fd = events[i].data.fd; // 取出 fd char buffer[1024]; ssize_t bytes_read; // ET 模式需循环读取所有数据 while ((bytes_read = read(client_fd, buffer, sizeof(buffer))) {  if (bytes_read > 0) { write(client_fd, buffer, bytes_read); // Echo 回显  } else if (bytes_read == 0 || (errno != EAGAIN && errno != EWOULDBLOCK)) { close(client_fd); // 关闭连接 break;  } } } // 处理连接关闭 else if (events[i].events & (EPOLLRDHUP | EPOLLHUP)) { close(events[i].data.fd); // 关闭失效连接 } } } close(server_fd); return 0;}
关键逻辑说明:
  1. 事件注册

    • 通过 epoll_ctl() 添加 fd 时,初始化 epoll_event 结构:
      ev.events = EPOLLIN | EPOLLET; // 订阅事件类型ev.data.fd = server_fd; // 存储关联的 fd
  2. 事件处理

    • epoll_wait() 返回就绪的 epoll_event 数组
    • 通过 events[i].data.fd 取出关联的 fd
    • 通过 events[i].events 判断具体事件类型
  3. 边缘触发 (ET) 要点

    • 必须循环读写直到返回 EAGAIN
    • 需配合非阻塞 fd 避免阻塞

常见使用技巧

  1. 自定义数据存储

    • 使用 data.ptr 存储复杂结构体(如连接上下文):
      struct connection { int fd; struct sockaddr_in addr;};struct connection *conn = malloc(sizeof(*conn));conn->fd = client_fd;ev.data.ptr = conn; // 存储指针
  2. 事件组合

    • 错误处理:EPOLLERR | EPOLLHUP
    • 读写监听:EPOLLIN | EPOLLOUT
  3. 触发模式选择

    • 水平触发 (LT):未处理事件会持续通知(默认)
    • 边缘触发 (ET):事件就绪时只通知一次(性能更高)

性能提示:ET 模式 + 非阻塞 I/O 是 epoll 高性能的关键组合,适合高并发场景。