目錄
1.select函數
1.1參數說明
2.poll函數
2.1參數說明
2.2傳回值
2.3事件标記
3.epoll函數
3.1參數說明
3.2傳回值
3.3與select&poll的差別
3.4epoll的優點
3.5epoll的模型
1.select函數
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout);
1.1參數說明
第1個參數nfds為fdset集合中最大描述符值加1,fdset是一個位數組,其大小限制為__FD_SETSIZE(1024),位數組的每一位代表其對應的描述符是否需要被檢查。
第2,3,4個參數表示需要關注讀、寫、錯誤事件的檔案描述符位數組,這些參數既是輸入參數也是輸出參數,可能會被核心修改用于标示哪些描述符上發生了關注的事件。是以每次調用select前都需要重新初始化fdset。
第5個參數為逾時時間,該結構會被核心修改,其值為逾時剩餘的時間。
select對應于核心中的sys_select調用,sys_select首先将第2,3,4個參數指向的fd_set拷貝到核心,然後對每個被SET的描述符調用進行poll輪詢,并記錄在臨時結果中(fdset),如果有事件發生,select會将臨時結果寫到使用者空間并傳回;當輪詢一遍後沒有任何事件發生時,如果指定了逾時時間,則select會睡眠到逾時,睡眠結束後再進行一次輪詢,并将臨時結果寫到使用者空間,然後傳回。
select傳回後,需要逐一檢查關注的描述符是否被SET(事件是否發生)。
2.poll函數
struct pollfd {
int fd; //檔案描述符
short events; //要求檢測的事件掩碼
short revents; //傳回的事件掩碼
}
typedef unsigned long nfds_t;
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
2.1參數說明
poll與select不同,通過一個pollfd數組向核心傳遞需要關注的事件,故沒有描述符個數的限制。pollfd中的events字段和revents分别用于标示關注的事件和發生的事件,故pollfd數組隻需要被初始化一次。
第1個參數用于存放需要檢測其狀态的socket描述符;每當調用這個函數之後,系統不會清空這個數組,操作起來比較友善;特别是對于socket連接配接比較多的情況下,在一定程度上可以提高處理的效率;這一點與select()函數不同,調用select()函數之後,select()函數會清空它所檢測的socket描述符集合,導緻每次調用select()之前都必須把socket描述符重新加入到待檢測的集合中;是以,select()函數适合于隻檢測一個socket描述符的情況,而poll()函數适合于大量socket描述符的情況。
poll的實作機制與select類似,其對應核心中的sys_poll,隻不過poll向核心傳遞pollfd數組,然後對pollfd中的每個描述符進行poll,相比處理fdset來說,poll效率更高。
poll傳回後,需要對pollfd中的每個元素檢查其revents值,來得知事件是否發生。
第2個參數用于标記數組fds中的結構體元素的總數量。
第3個參數是poll函數調用阻塞的時間,機關為毫秒。
2.2傳回值
>0: 數組fds中準備好讀、寫或出錯狀态的那些socket描述符的總數量。
==0: 數組fds中沒有任何socket描述符準備好讀、寫,或出錯;此時poll逾時,逾時時間是timeout毫秒;換句話說,如果所檢測的 socket描述符上沒有任何事件發生的話,那麼poll()函數會阻塞timeout所指定的毫秒時間長度之後傳回,如果timeout==0,那麼 poll() 函數立即傳回而不阻塞,如果timeout==INFTIM,那麼poll() 函數會一直阻塞下去,直到所檢測的socket描述符上的感興趣的事件發 生是才傳回,如果感興趣的事件永遠不發生,那麼poll()就會永遠阻塞下去。
-1: poll函數調用失敗,同時會自動設定全局變量errno。
如果待檢測的socket描述符為負值,則對這個描述符的檢測就會被忽略,也就是不會對成員變量events進行檢測,在events上注冊的事件也會被忽略,poll()函數傳回的時候,會把成員變量revents設定為0,表示沒有事件發生。
另外,poll() 函數不會受到socket描述符上的O_NDELAY标記和O_NONBLOCK标記的影響和制約,也就是說,不管socket是阻塞的還是非阻塞 的,poll()函數都不會收到影響;而select()函數則不同,select()函數會受到O_NDELAY标記和O_NONBLOCK标記的影 響,如果socket是阻塞的socket,則調用select()跟不調用select()時的效果是一樣的,socket仍然是阻塞式TCP通信,相反,如果socket是非阻塞的socket,那麼調用select()時就可以實作非阻塞式TCP通信。
是以poll() 函數的功能和傳回值的含義與 select() 函數的功能和傳回值的含義是完全一樣的,兩者之間的差别就是内部實作方式不一樣。
2.3事件标記
經常檢測的事件标記: POLLIN/POLLRDNORM(可讀)、POLLOUT/POLLWRNORM(可寫)、POLLERR(出錯)。
如果是對一個描述符上的多個事件感興趣的話,可以把這些常量标記之間進行按位或運算。例如:對socket描述符fd上的讀、寫、異常事件感興趣,就可以這樣做:
struct pollfd fds;
fds[nIndex].events=POLLIN | POLLOUT | POLLERR;
當 poll()函數傳回時,要判斷所檢測的socket描述符上發生的事件,可以這樣做:
struct pollfd fds;
檢測可讀TCP連接配接請求:
if((fds[nIndex].revents & POLLIN) == POLLIN) {
//接收資料/調用accept()接收連接配接請求
}
檢測可寫:
if((fds[nIndex].revents & POLLOUT) == POLLOUT) {
//發送資料
}
檢測異常:
if((fds[nIndex].revents & POLLERR) == POLLERR) {
//異常處理
}
3.epoll函數
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
3.1參數說明
epoll主要包含epoll_create, epoll_ctl以及epoll_wait三個函數。
epoll_create函數建立epoll檔案描述符,參數size并不是限制了epoll所能監聽的描述符最大個數,隻是對核心初始配置設定内部資料結構的一個建議。
當建立好epoll句柄後,會占用一個fd值,在linux下可以通過"cat /proc/程序id/fd/" 檢視。是以在使用完epoll後,必須調用close()關閉,否則可能導緻fd被耗盡。
使用例子:int epfd = epoll_create(int size);
epoll_ctl事件注冊函數,控制對指定描述符fd執行op操作。
第1個參數是epoll_create()的傳回值;
第2個參數表示動作,使用如下三個宏來表示:
EPOLL_CTL_ADD //注冊新的fd到epfd中;
EPOLL_CTL_MOD //修改已經注冊的fd的監聽事件;
EPOLL_CTL_DEL //從epfd中删除一個fd;
第3個參數是需要監聽的fd。
第4個參數是告訴核心需要監聽什麼事件。events可以是下面幾個宏的集合:
EPOLLIN //表示對應的檔案描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT //表示對應的檔案描述符可以寫;
EPOLLPRI //表示對應的檔案描述符有緊急的資料可讀(這裡應該表示有帶外資料到來);
EPOLLERR //表示對應的檔案描述符發生錯誤;
EPOLLHUP //表示對應的檔案描述符被挂斷;
EPOLLET //将epoll設為邊緣觸發(Edge Triggered)模式,這是相對于水準觸發(Level Triggered)來說的。
EPOLLONESHOT //隻監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,
//需要再次把這個socket加入到epoll隊列裡。
當對方關閉連接配接(FIN), EPOLLERR,都可以認為是一種EPOLLIN事件,在read的時候分别有0,-1兩個傳回值。
使用例子:
struct epoll_event ev;
ev.data.fd=listenfd; //設定與要處理的事件相關的檔案描述符
ev.events=EPOLLIN|EPOLLET; //設定要處理的事件類型
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); //注冊epoll事件
epoll_wait等待epfd上的io事件,最多傳回maxevents個事件。
第1個參數是epoll_create()的傳回值,即生成的epoll專用的檔案描述符;
第2個參數是用于回傳代處理事件的數組;
第3個參數是每次能處理的事件數,用于告訴核心這個events有多大,maxevents的值不能大于建立epoll_create()時的size;
第4個參數是等待I/O事件發生的逾時時間。機關是毫秒,0會立即傳回,-1将不确定,也有說法說是永久阻塞。
epoll_wait運作的原理是:等侍注冊在epfd上的socket fd的事件的發生,如果發生則将發生的socket fd和事件類型放入到events數組中。并且将注冊在epfd上的socket fd的事件類型給清空,是以如果下一個循環你還要關注這個socket fd的話,則需要用epoll_ctl(epfd, EPOLL_CTL_MOD, listenfd, &ev)來重新設定socket fd的事件類型。這時不用EPOLL_CTL_ADD,因為socket fd并未清空,隻是事件類型清空。這一步非常重要。
3.2傳回值
epoll_create傳回一個epoll專用的檔案描述符。它其實是在核心申請一空間,用來存放你想關注的socket fd上是否發生以及發生了什麼事件。
epoll_ctl執行成功時傳回0; 失敗傳回-1.
epoll_wait傳回需要處理的事件數目,若傳回0則表示已逾時。
3.3與select&poll的差別
在 select/poll中,程序隻有在調用一定的方法後,核心才對所有監視的檔案描述符進行掃描,而epoll事先通過epoll_ctl()來注冊一 個檔案描述符,一旦基于某個檔案描述符就緒時,核心會采用類似callback的回調機制,迅速激活這個檔案描述符,當程序調用epoll_wait() 時便得到通知。
epoll與select、poll不同,首先,其不用每次調用都向核心拷貝事件描述資訊,在第一次調用後,事件資訊就會與對應的epoll描述符關聯起來。其次,epoll不是通過輪詢,而是通過在等待的描述符上注冊回調函數,當事件發生時,回調函數負責把發生的事件存儲在就緒事件連結清單中,最後寫到使用者空間。
epoll傳回後,該參數指向的緩沖區中即為發生的事件,對緩沖區中每個元素進行處理即可,而不需要像poll、select那樣進行輪詢檢查。
3.4epoll的優點
1. 監視的描述符數量不受限制,它所支援的FD上限是最大可以打開檔案的數目,這個數字一般遠大于2048,舉個例子,在1GB記憶體的機器上大約是10萬左 右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統記憶體關系很大。select的最大缺點就是程序打開的fd是有數量限制的。這對于連接配接數量比較大的伺服器來說根本不能滿足。雖然也可以選擇多程序的解決方案( Apache就是這樣實作的),不過雖然linux上面建立程序的代價比較小,但仍舊是不可忽視的,加上程序間資料同步遠比不上線程間同步的高效,是以也不是一種完美的方案。
2. IO的效率不會随着監視fd的數量的增長而下降。epoll不同于select和poll輪詢的方式,而是通過每個fd定義的回調函數來實作的。隻有就緒的fd才會執行回調函數。
3. 支援電平觸發和邊沿觸發(隻告訴程序哪些檔案描述符剛剛變為就緒狀态,它隻說一遍,如果我們沒有采取行動,那麼它将不會再次告知,這種方式稱為邊緣觸發)兩種方式,理論上邊緣觸發的性能要更高一些,但是代碼實作相當複雜。
4. mmap加速核心與使用者空間的資訊傳遞。epoll是通過核心與使用者空間mmap同一塊記憶體,避免了無謂的記憶體拷貝。
3.5epoll的模型
EPOLL事件有兩種模型 Level Triggered (LT) 和 Edge Triggered (ET):
LT(level triggered,水準觸發模式)是預設的工作方式,并且同時支援 block 和 non-block socket。在這種做法中,核心告訴你一個檔案描述符是否就緒了,然後你可以對這個就緒的fd進行IO操作。如果你不作任何操作,核心還是會繼續通知你的,是以,這種模式程式設計出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表。
ET(edge-triggered,邊緣觸發模式)是高速工作方式,隻支援no-block socket。在這種模式下,當描述符從未就緒變為就緒時,核心通過epoll告訴你。然後它會假設你知道檔案描述符已經就緒,并且不會再為那個檔案描述符發送更多的就緒通知,等到下次有新的資料進來的時候才會再次出發就緒事件。