1. 簡介
在《UNIX網絡編程》一書中介紹瞭如何使用select/poll來實現I/O多路複用,簡而言之就是通過內核的一種機制,監視多個文件描述符,一旦某個文件描述符處於就緒狀態,就通知用戶程序進行相應的讀寫操作,這樣用戶程序就不用阻塞在每一個文件描述符上。
epoll相對於select/poll來說有很大優勢:
(1)不再需要每次把fd集合從用戶態拷貝到內核態。
(2)不再需要在每次就緒時遍歷fd集合中的所有fd來檢查哪些fd處於就緒狀態,epoll只返回就緒的fd集合。
(3)select一般只支持1024個文件描述符,而epoll沒有類似的限制。
2. epoll相關函數
使用epoll只需要記住3個系統調用函數。
2.1 int epoll_create(int size)
創建一個epoll實例,從2.68的Linux內核開始,size參數不再生效,內核會動態分配所需的數據結構。失敗返回-1,成功則該函數會返回一個文件描述符,並佔用一個fd值,所以在使用完之後要記得close該文件描述符。
2.2 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
用於對epoll實例執行不同的操作的函數。
epfd:使用epoll_create()返回的文件描述符
op:不同的操作,用三個宏表示不同的操作
EPOLL_CTL_ADD:註冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;
EPOLL_CTL_DEL:從epfd中刪除指定fd;
fd:要監聽的文件描述符
event:event 是與指定fd關聯的epoll_event結構,包含了監聽事件,附加數據
struct epoll_event 的結構如下:
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
}epoll_data_t;
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
這裏需要特別注意的是epoll_data_t是一個union結構,fd和ptr指針只能使用一個,通常我們使用void *ptr存儲需要附加的用戶數據結構,然後在用戶數據結構中存儲int型的fd,這樣在epoll_wait調用後就仍然能獲得該註冊事件對應的文件描述符。
events可以是如下值的集合:
EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉)
EPOLLOUT:表示對應的文件描述符可以寫
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來)
EPOLLERR:表示對應的文件描述符發生錯誤
EPOLLHUP:表示對應的文件描述符被掛斷
EPOLLET: 將EPOLL設爲邊緣觸發(EdgeTriggered)模式,這是相對於水平觸發(Level Triggered)來說的
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裏
2.3 int epoll_wait(int epfd, struct epoll_event * events, int maxevents,int timeout)
該函數等待epoll實例中的fd集合有就緒事件。
epfd:使用epoll_create()返回的文件描述符
events:指向處於就緒狀態的事件集合
maxevents:最多maxevents數量的事件集合會被返回
timeout:超時時間,單位爲毫秒;指定爲-1沒有超時時間,指定爲0則立即返回並返回0
如果成功,返回已就緒的事件的個數;如果達到超時時間仍然沒有就緒事件,返回0;如果出現錯誤,返回-1並置errno值
3. LT和ET兩種工作方式
epoll 默認使用LT的工作方式,當指定事件就緒時,內核通知用戶進行操作,如果你只處理了部分數據,只要對應的套接字緩衝區中還有剩餘數據,下一次內核仍然還會繼續通知用戶去進行處理,所以使用這種模式來寫程序較爲簡單。
ET工作方式是一種告訴工作方式,只能使用非阻塞socket,它的效率要比LT方式高。當一個新事件就緒時,內核通知用戶進行操作,如果這時用戶沒有處理完緩衝區的數據,緩衝區中剩餘的數據就會丟失,用戶無法從下一次epoll_wait調用中獲取到這個事件。
舉個例子,可以指定事件爲 EPOLLIN| EPOLLET 來使用ET工作方式獲取指定文件描述符的可讀事件。
在該事件就緒後,需要不斷調用read函數來獲取緩衝區數據,直到產生一個EAGAIN錯誤或者read函數返回的讀取到的數據長度小於請求的數據長度時才認爲此事件處理完成。write也是一樣的處理方式。