Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

Linux epoll 模型詳解:LT/ET 模式、API 全景與高併發服務器實戰

編輯:Linux 教學

前言

epoll 是 Linux 2.5.44 引入的高效 I/O 多路複用機制,解決 select/poll 在大連接數下的 O(n) 掃描、重複拷貝、重複掛載隊列三大痛點。本文系統講解 epoll 創建動機、核心優勢、LT/ET 兩種觸發模式、三大 API 詳解、完整服務器範例代碼與關鍵優化要點。

一、select/poll 致命缺陷回顧

  1. FD_SETSIZE 限制:預設 1024(可改內核重編,但效能下降)。
  2. 線性掃描:每次調用遍歷所有 fd,活躍連接少時極度浪費 CPU。
  3. 重複拷貝/掛載:用戶態↔核心態拷貝 fd 集合,每次掛載/移除等待隊列。

二、epoll 四大核心優勢

優勢技術實現
無 FD 數量上限支援至系統 /proc/sys/fs/file-max(1GB 記憶體約 10 萬)
效能不隨 FD 數線性下降僅處理「就緒」fd,核心以回調函數驅動,非輪詢
mmap 零拷貝核心與用戶空間共享就緒事件環狀緩衝區
內核可調優TCP 記憶體池、listen backlog、NAPI 驅動等可運行時調優

三、兩大觸發模式:LT vs ET

特性LT (Level Triggered)ET (Edge Triggered)
預設模式是否(需指定 EPOLLET)
支援阻塞/非阻塞皆支援僅支援非阻塞
就緒通知語意「狀態就緒」持續通知,直到處理完「狀態變化」瞬間通知一次,不再重複
程式設計難度低,不易漏讀高,必須循環讀寫至 EAGAIN
典型適用一般網路服務極高併發、零拷貝、狀態機驅動

ET 模式核心循環:while ((n = read(fd, buf, sizeof(buf))) > 0) process(buf); if (n == -1 && errno != EAGAIN) error;

四、三大核心 API 詳解

1. int epoll_create(int size)

  • 創建 epoll 實例,size 提示監聽規模(內核 2.6.8+ 忽略,僅驗證 >0)。
  • 返回 epfd(佔用一個 fd),用畢必須 close() 避免洩漏。

2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

op宏用途
ADDEPOLL_CTL_ADD註冊新 fd
MODEPOLL_CTL_MOD修改已註冊 fd 事件
DELEPOLL_CTL_DEL移除 fd(event 可為 NULL)

struct epoll_event 關鍵欄位:

uint32_t events;   // 事件掩碼:EPOLLIN/EPOLLOUT/EPOLLERR/EPOLLET/EPOLLONESHOT...
epoll_data_t data; // 用戶自定義資料:ptr/fd/u32/u64

3. int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

  • 阻塞等待就緒事件,timeout:-1 永久、0 立即返回、>0 毫秒。
  • 返回就緒事件數,events 陣列由核心填充(mmap 共享記憶體,零拷貝)。

五、高併發 Echo 服務器完整範例(ET 模式)

#include 
#include 
#include 
#include 
#define MAXLINE 5
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5000

void setnonblocking(int sock) {
    int opts = fcntl(sock, F_GETFL);
    opts |= O_NONBLOCK;
    fcntl(sock, F_SETFL, opts);
}

int main(int argc, char *argv[]) {
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    setnonblocking(listenfd);

    struct sockaddr_in servaddr = {0};
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);
    bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    listen(listenfd, LISTENQ);

    int epfd = epoll_create(256);
    struct epoll_event ev, events[20];
    ev.data.fd = listenfd;
    ev.events = EPOLLIN | EPOLLET;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);

    for (;;) {
        int nfds = epoll_wait(epfd, events, 20, 500);
        for (int i = 0; i < nfds; ++i) {
            if (events[i].data.fd == listenfd) {          // 新連接
                int connfd = accept(listenfd, NULL, NULL);
                setnonblocking(connfd);
                ev.data.fd = connfd;
                ev.events = EPOLLIN | EPOLLET;
                epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
            } else if (events[i].events & EPOLLIN) {      // 可讀
                int sockfd = events[i].data.fd;
                char line[MAXLINE];
                int n = read(sockfd, line, MAXLINE);
                if (n <= 0) { close(sockfd); events[i].data.fd = -1; continue; }
                line[n] = '\0';
                std::cout << "read: " << line << std::endl;
                ev.data.fd = sockfd;
                ev.events = EPOLLOUT | EPOLLET;           // 切換到可寫
                epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
            } else if (events[i].events & EPOLLOUT) {     // 可寫
                int sockfd = events[i].data.fd;
                write(sockfd, line, n);                   // 此處 line/n 需保存上下文
                ev.data.fd = sockfd;
                ev.events = EPOLLIN | EPOLLET;            // 切回可讀
                epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
            }
        }
    }
    return 0;
}

關鍵點註解:

  1. setnonblocking:ET 模式強制非阻塞,否則 read/write 會阻塞整個事件循環。
  2. EPOLLET:邊緣觸發,配合非阻塞實現高效零拷貝。
  3. epoll_ctl MOD:動態切換關注事件(讀↔寫),避免忙輪詢。
  4. 生產級代碼需維護 sockfd → buffer 映射,處理分包/黏包、錯誤碼、關閉序列。

六、避坑指南與優化建議

  1. EPOLLONESHOT:多執行緒處理同一 fd 時,防止多線程並發處理同一事件,處理完再 MOD 重新註冊。
  2. 大量連接:調大 /proc/sys/fs/file-max、ulimit -n、somaxconn、TCP 記憶體參數。
  3. 邊緣觸發讀寫循環:必須循環讀寫至 EAGAIN,否則會漏事件導致連接卡死。
  4. 水平觸發兜底:不確定時優先用 LT,效能差異微小但極難出錯。

總結

epoll 以「事件驅動 + 就緒鏈表 + mmap 共享」三大創新,徹底解決 select/poll 規模瓶頸。掌握 epoll_create/ctl/wait 三板斧、LT/ET 選型原則、非阻塞邊緣觸發讀寫循環,即可構建百萬級併發網路服務。

Copyright © Windows教程網 All Rights Reserved