三者皆由應用程式將一組檔案描述符 (FD) 交給核心監控,核心在任一 FD 就緒 (可讀/可寫/異常) 時通知應用程式,應用程式再自行發起實際讀寫 (阻塞或非阻塞)。核心不負責資料搬移,故屬同步 I/O。
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
FD_SETSIZE (通常 1024),可修改重編核心但效能不升反降。fd_set 從使用者空間複製到核心空間 (O(n)),返回後再複製回來並全掃描 找就緒 FD (O(n))。FD_SET/FD_CLR/FD_ISSET 操作。int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd { int fd; short events; short revents; };
RLIMIT_NOFILE 與記憶體)。events 輸入關注事件,revents 輸出實際事件,避免 select 的輸入輸出共用位圖問題。fds 陣列 (O(n)),大量連線時效能線性下降。事件驅動 + 回調機制,避免全掃描,是 Linux 高併發網路編程標準選擇。
// 1. 建立 epoll 實例,返回一個 FD (epfd)
int epoll_create(int size); // size 為初始分配提示,現核心動態擴展
// 建議用 epoll_create1(EPOLL_CLOEXEC)
// 2. 註冊/修改/刪除 FD 及關注事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
op: EPOLL_CTL_ADD / EPOLL_CTL_MOD / EPOLL_CTL_DEL
struct epoll_event {
uint32_t events; // EPOLLIN, EPOLLOUT, EPOLLET, EPOLLONESHOT...
epoll_data_t data; // 用戶自訂資料 (ptr/fd/u32/u64)
};
// 3. 等待就緒事件
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
// 返回就緒事件數量,events 陣列填入就緒事件資訊
// timeout: -1 永久阻塞, 0 非阻塞輪詢, >0 毫秒超時
EPOLLIN:可讀 (含對端正常關閉 FIN)EPOLLOUT:可寫EPOLLET:邊緣觸發 (Edge Triggered) — 僅在狀態變化瞬間通知一次,需配合非阻塞 IO 循環讀寫至 EAGAIN,適合高併發。EPOLLONESHOT:一次性通知,處理完需再次 epoll_ctl MOD 重新啟用,常用於執行緒池分發任務避免驚群。| 模式 | 通知時機 | 編程複雜度 | 適用場景 |
|---|---|---|---|
| LT (水平觸發) | FD 就緒狀態持續期間反覆通知 | 低,類似 select/poll | 一般應用、快速原型 |
| ET (邊緣觸發) | FD 從未就緒→就緒的瞬間通知一次 | 高,必須非阻塞 + 循環讀寫 | 高併發、長連線 (Nginx, Redis, Netty) |
| 指標 | select | poll | epoll |
|---|---|---|---|
| FD 數量上限 | 1024 (預設) | 無硬性上限 | 無硬性上限 (受限於系統 max open files) |
| 時間複雜度 | O(n) 全掃描 | O(n) 全掃描 | O(1) 就緒事件回調 |
| 核心/用戶空間複製 | 每次三組位圖雙向複製 | 每次陣列雙向複製 | 僅就緒事件複製 (透過共享記憶體 epoll_event) |
| 大量閒置連線 | 極差 | 極差 | 優異 (僅活躍連線觸發回調) |
| 可移植性 | 跨平台 (POSIX) | 跨平台 (POSIX) | Linux 專有 |
O_NONBLOCK;ET 模式必須循環 read/write 至 EAGAIN;定期檢查 epoll_wait 返回值處理錯誤/關閉;使用 EPOLLONESHOT 配合執行緒池避免同一連線並發處理。