在深入理解select、poll和epoll之間的區別之前,首先要了解什么是IO多路復用模型。
IO多路復用
簡單來說,IO多路復用是指內核一旦發現進程指定的一個或者多個IO條件准備就緒,它就通知該進程去進行IO操作。
詳細的描述可以參考IO模型。select、poll和epoll都是提供I/O多路復用的解決方案。
select
- 函數
int select(int maxfdp, fd_set *readset, fd_set *writeset,
fd_set *exceptset, struct timeval *timeout);
- 基本原理:select 函數監視的文件描述符分3類,分別是
writefds
、readfds
、和exceptfds
。調用select時會被阻塞,直到有fd就緒(讀、寫、或者異常),或者超時(timeout
指定等待時間,如果立即返回設為null
即可)函數返回一個大於0
的值,然后通過遍歷文件描述符集合fd_set
,來找到就緒的描述符 - 說明:
maxfdp
是一個整數值,是指集合中所有文件描述符的范圍,即所有文件描述符的最大值加1。fd_set
是以位圖的形式來存儲這些文件描述符。maxfdp
也就是定義了位圖中有效地位的個數 - 時間復雜度:
O(n)
,n
為文件描述符集合fd_set
的大小 - 文件描述符最大限制:
1024
poll
- 函數
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
- 基本原理:和select 函數很相似,定義了一個
struct pollfd
結構類型的數組,用於存放需要檢測其狀態的所有文件描述符。調用poll函數之后,系統不會清空這個數組。特別是對於文件描述符比較多的情況下,在一定程度上可以提高處理的效率。這一點與select()函數不同,調用select()函數之后,select()函數會清空它所檢測的文件描述符集合,導致每次調用select()之前都必須把文件描述符重新加入到待檢測的集合中。因此,select()函數適合於只檢測一個文件描述符的情況,而poll()函數適合於大量文件描述符的情況 - 時間復雜度:
O(n)
,n
為文件描述符集合的大小 - 文件描述符最大限制:無限制,
fds
是一個鏈表
epoll
- 函數
// 建立一個epoll對象,參數size是內核保證能夠正確處理的最大句柄數
int epoll_create(int size);
// 操作上面建立的epoll
// 例如,將剛建立的socket加入到epoll中讓其監控,或者
// 把 epoll正在監控的某個socket句並移出epoll
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 在指定的timeout時間內,當所有句柄中有事件發生時,就返回給用戶態的進程
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
- 基本原理:將所有的文件描述符
fd_set
存儲在共享內存(用戶空間和內核空間都可以直接訪問)。調用epoll_create
函數創建epoll對象,並以紅黑樹的結構存儲在內核空間,epoll_ctl
函數用來在紅黑樹中添加或者注銷待監視文件描述符,最后調用epoll_wait
函數直到有就緒的文件描述符立即返回給用戶態進程
- 時間復雜度:
O(1)
- 文件描述符最大限制:能打開的fd的上限遠大於
1024
(1G
的內存上能監聽約10w+
)
epoll的兩種工作方式
- 水平觸發(LT):若就緒的事件一次沒有處理完所有要做的事件,就會一直去處理。即就會將沒有處理完的事件繼續放回到就緒隊列之中(即那個內核中的鏈表),一直進行處理
- 邊緣觸發(ET) :就緒的事件只能處理一次,若沒有處理完會在下次的其它事件就緒時再進行處理。而若以后再也沒有就緒的事件,那么剩余的那部分數據也會隨之而丟失。
ET模式的效率比LT模式的效率要高很多。只是如果使用ET模式,就要保證每次進行數據處理時,要將其處理完,不能造成數據丟失,這樣對編寫代碼的人要求就比較高。 為了保證數據的完整性,ET模式只支持非阻塞的讀寫。
select、poll和epoll對比
實現機制 | 時間復雜度 | 連接數 | 傳遞方式 |
---|---|---|---|
select | O(n) |
1024 |
內核-->用戶 |
poll | O(n) |
無限制 | 內核-->用戶 |
epoll | O(1) |
很大 | 共享內存 |
在select和poll中,進程只有在調用方法后,內核才對所有監視的文件描述符進行掃描,發現有任何一個文件描述符就緒或者超時就立刻返回。epoll采用基於事件的就緒通知方式,事先通過epoll_ctl()來注冊一個文件描述符,一旦基於某個文件描述符就緒時,內核會采用類似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便得到通知。