一文教你大廠如何設計基於 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 有以下兩個優勢:
- 支持理論上無限大的 socket 描述符
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);
};
-
recvData 函數作爲純虛函數用於接受客戶端的請求。成功返回讀取的字節數,失敗返回 - 1。
-
sendData 函數作爲純虛函數用於回覆客戶端的請求。成功返回寫出去的字節數,失敗返回 - 1。
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