epoll_event
數據結構詳解
在 Linux 的 I/O 多路復用機制 epoll
中,epoll_event
是關鍵的數據結構,用于描述文件描述符(fd)上的事件和關聯數據。其定義在頭文件 <sys/epoll.h>
中:
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;
核心字段解析:
-
events
(事件標志位)EPOLLIN
:fd 可讀(有數據到達)EPOLLOUT
:fd 可寫(可發送數據)EPOLLERR
:fd 發生錯誤EPOLLHUP
:fd 被掛斷(對端關閉連接)EPOLLET
:啟用邊緣觸發模式(默認水平觸發)EPOLLRDHUP
:流套接字對端關閉連接(或半關閉)
-
data
(用戶數據聯合體)- 常用
fd
或ptr
存儲與事件相關的自定義數據(如連接上下文)
- 常用
使用案例:基于 ET 模式的 Echo 服務器
以下示例展示 epoll_event
在非阻塞 TCP 服務器中的典型用法(邊緣觸發模式):
#include <sys/epoll.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>#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 到 epollev.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);// 注冊客戶端 fdev.events = EPOLLIN | EPOLLET | EPOLLRDHUP;ev.data.fd = client_fd; // 存儲客戶端 fdepoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &ev);}// 處理客戶端數據else if (events[i].events & EPOLLIN) {int client_fd = events[i].data.fd; // 取出 fdchar 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;
}
關鍵邏輯說明:
-
事件注冊
- 通過
epoll_ctl()
添加 fd 時,初始化epoll_event
結構:ev.events = EPOLLIN | EPOLLET; // 訂閱事件類型 ev.data.fd = server_fd; // 存儲關聯的 fd
- 通過
-
事件處理
epoll_wait()
返回就緒的epoll_event
數組- 通過
events[i].data.fd
取出關聯的 fd - 通過
events[i].events
判斷具體事件類型
-
邊緣觸發 (ET) 要點
- 必須循環讀寫直到返回
EAGAIN
- 需配合非阻塞 fd 避免阻塞
- 必須循環讀寫直到返回
常見使用技巧
-
自定義數據存儲
- 使用
data.ptr
存儲復雜結構體(如連接上下文):struct connection {int fd;struct sockaddr_in addr; };struct connection *conn = malloc(sizeof(*conn)); conn->fd = client_fd; ev.data.ptr = conn; // 存儲指針
- 使用
-
事件組合
- 錯誤處理:
EPOLLERR | EPOLLHUP
- 讀寫監聽:
EPOLLIN | EPOLLOUT
- 錯誤處理:
-
觸發模式選擇
- 水平觸發 (LT):未處理事件會持續通知(默認)
- 邊緣觸發 (ET):事件就緒時只通知一次(性能更高)
性能提示:ET 模式 + 非阻塞 I/O 是 epoll 高性能的關鍵組合,適合高并發場景。