乾貨| 為什麼你的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 儲存了觸發事件的某個檔案描述符相關的資料。定義如下

這裡的關鍵設計是把 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 類關係圖如下:

乾貨| 為什麼你的epoll總是被低階工程師所用?原因都在這裡了

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

Agent 類

Agent 類宣告如下:

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()函式。

60+專家,13個技術領域,CSDN 《IT 人才成長路線圖》重磅來襲!