一文教你大廠如何設計基於 Epoll 的網絡通信模型

作者 | 神技圈子       責編 | 歐陽姝黎

出品 | CSDN 博客

背景

網上講 Epoll 的很多,但是都僅僅停留在簡單的實例使用。但是在真正的工程應用中不可能就使用這種簡單的過程式開發。那麼 Epoll 到底該怎麼用呢?下面我們就來好好講講。

Epoll 的優勢

傳統的處理網絡 I/O 的多進程、多線程同步 I/O,或者是單線程的 select 和 poll 的事件驅動模型。其中多線程和多進程同步阻塞網絡 I/O 技術,具有模型直觀, 使用方便等優點,但當處理高併發的網絡連接時,因爲存在 Fork(線程池可部分避免) 和上下文切換操作產生較大的系統開銷;同時內存開銷也較大,不能滿足服務器性能要求,適用於併發數不高以及服務器負載不大的場合。因此,爲了提升系統的高併發情況下的性能和吞吐率,一般採用 IO 多路複用模型。IO 多路複用包括 Select,Poll 和 Epoll 三種方式,Epoll 作爲 Linux 內核爲處理大批文件描述符而改進的 poll。相對於 select 和 poll,Epoll 有以下兩個優勢:

select 限制了每個進程打開的 socket 描述符,例如 Linux 系統在 linux/include/linux/posix_types.h 中定義了_FD_SETSIZE 爲 1024,如下圖

即在 Linux 系統中 Select 最大隻能支持 1024 個描述符,當需要監聽 1024 個以上的描述符時,Select 函數就會監聽出錯。而 Epoll 使用紅黑樹管理註冊的描述符,理論上能監聽無限個描述符,現實中會收到內存的限制。

select 和 poll 都是通過鏈表管理註冊好的描述符,每次當有描述符監聽到讀寫事件發生時,select 和 poll 都需要遍歷整個鏈表從而找到有事件發生的描述符,在活動描述符較少的情況下,這種方案是非常低效的。Epoll 採用紅黑樹管理描述符,如果有描述符監聽到讀寫時間發生,Epoll 會通過回調函數將該描述符插入到就緒隊列中,即每次 Epoll 掃描的只是就緒隊列中的發生讀寫事件的描述符。

設計思想

下面我們就來講講。

在 Epoll 中有個重要的結構體 epoll_event,它被用於註冊所感興趣的事件和回傳所發生的事件。它的定義如下:

它當中的 epoll_data_t 保存了觸發事件的某個文件描述符相關的數據。定義如下

typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;

這裏的關鍵設計是把 epoll_data_t 中的地址指針 ptr 同一個通信實體交互的通信單元類 Agent 進行綁定。爲什麼這麼做呢?

我們把每個通信實體對應於一個 Agent 實例。當這個套接字或者文件描述符上有 I/O 事件到達時,Epoll 會返回這個套接字所綁定的地址指針,這裏把這個地址指針指向這個套接字或者文件描述符對應的 Agent 實例,這樣就可以返回 Agent 實例的地址,然後根據 I/O 事件的不同調用 Agent 裏面對應的處理函數處理與通信實體間的交互。Agent 類處理的交互一般包括讀寫時間處理,以及 Agent 的啓動和停止。

設計方法

主要的類有兩個 Epoll 和 Agent:

Epoll 類封裝事件驅動核心,負責事件通知。讀寫事件發生後,由 Agent 處理網絡讀寫。

Agent 類是事件處理器的基類。TCPListenAgent 和 TCPAgent 繼承自 Agent,分別處理 TCP 監聽套接字和普通 TCP 連接的網絡收發。

Epoll 和 Agent 類關係圖如下:

下面我們來介紹下這些類的設計

Agent 類

Agent 類聲明如下:

 class Agent
  {
      protected:
             int m_iConnect;  //套接字連接狀態
              uint32_t m_ID; //Agent標識符,方便管理
     public:
           Agent(){}
           virtual ~Agent(){}
           virtual int SendData(void) = 0;                                                                                                       
           virtual int RecvData(void) = 0;
           int getState()const;
           virtual int wirteback(bool result);
  };

TCPListenAgent 類

主要負責處理客戶端發送過來的 TCP 連接請求。該類聲明如下:

TCPListenAgent 類主要實現 Agent 類提供的兩個純虛函數,這裏列出 TCPListenAgent 類提供的主要方法:

init 函數用於初始化 TCPListenAgent 本身。初始化成功返回 true,失敗返回 false。

recvData 函數用於處理客戶端發送來的 TCP 連接請求。

Epoll 類

Epoll 類採用設計模式中的單例模式,對於整個程序,全局僅有唯一的一個 Epoll 實例。Epoll 類的聲明如下

epollInitial 函數用於初始化 Epoll 對象, 參數 size 是 Epoll 監聽隊列的長度。

doEvent 函數用於對 Epoll 事件進行操作,增加、刪除或者修改。參數 agentPtr 具體的 Agent 對象,fd 爲需要加入 Epoll 的描述符,op 爲 Epoll 的具體操作可傳入參數包括 EPOLL_ADD,EPOLL_CTL,EPOLL_DEL,event 爲要監聽的 Epoll 事件。

run 函數用於執行 EPOLL 整個運行流程。函數中主要調用 epol_wait 函數,當 struct epoll_event 的 events 判斷是 EPOLLIN 時,Agent 對象調用 recvData 函數,如果是 EPOLLOUT 事件時調用 sendData() 函數。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/g_NTJJik76xGX2F9Fmq3LQ