select/poll/epoll對比分析
select/poll/epoll都是IO多路複用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程式進行讀或寫操作。本質上select/poll/epoll都是同步I/O,即讀寫是阻塞的。
一、select
原型:
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
從select函式監控3類檔案描述符:writefds、readfds、exceptfds。呼叫select函式後會阻塞,直到描述符準備就緒(有資料可讀、可寫、或者出現異常)或者超時,函式便返回。當select函式返回後,可以通過遍歷描述符集合,找到就緒的描述符。
select缺點
- 單程序能夠監控的檔案描述符的數量存在最大限制,在Linux上一般為1024,可以通過修改巨集定義增大上限,但同樣存在效率低的弱勢;
- IO效隨著監視的描述符數量的增長,其效率也會線性下降;
二、poll
原型:
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
其中pollfd表示監視的描述符集合,如下
struct pollfd {
int fd; //檔案描述符
short events; //監視的請求事件
short revents; //已發生的事件
};
pollfd結構包含了要監視的event和發生的event,並且pollfd並沒有最大數量限制(但數量過大同樣會導致性下降)。 和select函式一樣,當poll函式返回後,可以通過遍歷描述符集合,找到就緒的描述符。
從上面看,select和poll都需要在返回後,通過遍歷檔案描述符來獲取已經就緒的socket。事實上,同時連線的大量客戶端在一時刻可能只有很少的處於就緒狀態,因此隨著監視的描述符數量的增長,其效率也會線性下降。
三、epoll
epoll是在2.6核心中提出的,是select和poll的增強版。相對於select和poll來說,epoll更加靈活,沒有描述符數量限制。epoll使用一個檔案描述符管理多個描述符,將使用者空間的檔案描述符的事件存放到核心的一個事件表中,這樣在使用者空間和核心空間的copy只需一次。epoll機制是Linux最高效的I/O複用機制,在一處等待多個檔案控制代碼的I/O事件。
select/poll都只有一個方法,而epoll的操作過程有3個方法,分別是epoll_create()
, epoll_ctl()
,epoll_wait()
。
3.1 epoll_create()
int epoll_create(int size);
用於建立一個epoll的控制代碼,size是指監聽的描述符個數, 現在核心支援動態擴充套件,該值的意義僅僅是初次分配的fd個數,後面空間不夠時會動態擴容。 當建立完epoll控制代碼後,佔用一個fd值.
ls /proc/<pid>/fd/ //可通過終端執行,看到該fd
使用完epoll後,必須呼叫close()關閉,否則可能導致fd被耗盡。
3.2 epoll_ctl()
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
用於對需要監聽的檔案描述符(fd)執行op操作,比如將fd加入到epoll控制代碼。
- epfd:是epoll_create()的返回值;
- op:表示op操作,用三個巨集來表示,分別代表新增、刪除和修改對fd的監聽事件;
- EPOLL_CTL_ADD(新增)
- EPOLL_CTL_DEL(刪除)
- EPOLL_CTL_MOD(修改)
- fd:需要監聽的檔案描述符;
-
epoll_event:需要監聽的事件,struct epoll_event結構如下:
struct epoll_event { __uint32_t events; /* Epoll事件 */ epoll_data_t data; /*使用者可用資料*/ };
events可取值:(表示對應的檔案描述符的操作)
- EPOLLIN :可讀(包括對端SOCKET正常關閉);
- EPOLLOUT:可寫;
- EPOLLERR:錯誤;
- EPOLLHUP:中斷;
- EPOLLPRI:高優先順序的可讀(這裡應該表示有帶外資料到來);
- EPOLLET: 將EPOLL設為邊緣觸發模式,這是相對於水平觸發來說的。
- EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後就不再監聽該事件
3.3 epoll_wait()
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
- epfd:等待epfd上的io事件,最多返回maxevents個事件;
- events:用來從核心得到事件的集合;
- maxevents:events數量,該maxevents值不能大於建立epoll_create()時的size;
- timeout:超時時間(毫秒,0會立即返回)。
該函式返回需要處理的事件數目,如返回0表示已超時。
四、對比
在 select/poll中,程序只有在呼叫一定的方法後,核心才對所有監視的檔案描述符進行掃描,而epoll事先通過epoll_ctl()來註冊一個檔案描述符,一旦基於某個檔案描述符就緒時,核心會採用類似callback的回撥機制,迅速啟用這個檔案描述符,當程序呼叫epoll_wait() 時便得到通知。(此處去掉了遍歷檔案描述符,而是通過監聽回撥的的機制。這正是epoll的魅力所在。)
epoll優勢
-
監視的描述符數量不受限制,所支援的FD上限是最大可以開啟檔案的數目,具體數目可以
cat /proc/sys/fs/file-max
檢視,一般來說這個數目和系統記憶體關係很大,以3G的手機來說這個值為20-30萬。 -
IO效率不會隨著監視fd的數量增長而下降。epoll不同於select和poll輪詢的方式,而是通過每個fd定義的回撥函式來實現的,只有就緒的fd才會執行回撥函式。
如果沒有大量的idle-connection或者dead-connection,epoll的效率並不會比select/poll高很多,但是當遇到大量的idle-connection,就會發現epoll的效率大大高於select/poll。