select和epoll都是提供多路I/O復用的手段,以前我在學習網絡編程過程中只使用過select(主要是學習的《UNIX網絡編程這本書》),后來才知道還有一種算是更高效的I/O復用的方法叫做epoll,於是今天照着網上的教程擼了一遍代碼先了解一下epoll的使用,下面記錄一下今天學習過程中我覺得還蠻重要的點。
參考的博客:http://blog.csdn.net/ljx0305/article/details/4065058
1、select和epoll
首先是select和epoll方式的區別,在使用select的時候,有一個叫做fd_set的集合,這個集合用來存放所有需要進行復用的I/O(文件描述符),select首先會將這個fd_set集合拷貝進內核,然后內核就會遍歷這個集合(阻塞)直到某一個文件描述符滿足條件(可讀、可寫、異常)才返回。select的這種復用方式當文件描述符的數量很大的時候就會變得很低效(每次select都要把所有的fd拷貝進內核,開銷非常大,同時遍歷fd集合的開銷也很大),並且select還有一個非常大的限制就是它支持的文件描述符數量有上限,默認是1024(想要修改這個值需要重新編譯內核= =.)。
epoll不知道是不是針對select的缺點才提出的,因為epoll很好地解決了select存在的問題。
因為之前沒有接觸過epoll,所以首先還是來好好地回味一下epoll使用過程中需要掌握的三個函數:
1 int epoll_create(int size); 2 3 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 4 5 int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout);
函數的功能其實已經很好地體現在它的名字上了,epoll_create()負責創建文件描述符集合,epoll_ctl()(“control”)負責操作這個文件描述符集合(插入,刪除,更改等),epoll_wait()負責等待某個文件描述符滿足條件返回,個人感覺函數名字還是很好記的,就是后兩個函數的參數多了一點,尤其是還有一個epoll_event結構體,需要詳細了解。
- int epoll_create(int size);
參數size告訴內核epoll監聽的數量有多大,需要注意的是這個函數返回的也是一個文件描述符(就是epoll后兩個函數中用到的參數epfd),我個人覺得它應該是代表了內核中的某一個文件描述符集合。為什么需要返回這么一個文件描述符呢,這與epoll的實現機制有關,也就是為什么epoll會比select高效的原因。上面提到select每次操作時都要把所有文件描述符拷貝進內核,再由內核輪詢選擇出滿足條件的文件描述符。epoll也需要將文件描述符先拷貝進內存,但是它只做一次,epoll_create()就好像先在內核中開辟出一個固定大小的空的文件描述符集合,之后再將相應的文件描述符放進去或者從中將某一個文件描述符刪掉。簡單的來說,就是select拷貝進內核的文件描述符集在輪詢完之后就沒了,所以每次使用都需要再一次拷貝,而epoll拷貝進內核的文件描述符集在epoll使用期間會一直存在,並且就是由epoll_create()返回的這個文件描述符來標記的(為了后續的使用)。
- int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll_ctl()函數就是用來對內核中的那個文件描述符集來進行操作的,第一個參數就是epoll_create()返回的文件描述符,表示該操作是在哪個文件描述符集合上進行的。
第二個參數op(option),顧名思義,就是表示想做什么樣的操作,這個參數有三個宏可以選擇:
EPOLL_CTL_ADD //添加新的fd EPOLL_CTL_MOD //修改現有的fd EPOLL_CTL_DEL //刪除現有的fd
添加和刪除比較好理解,修改具體是做什么的我沒有實際使用到(我想應該是用來修改fd的監聽事件的)。
第三個參數就是需要進行操作的文件描述符值。
這個函數中比較重要的就是這第四個參數了struct epoll_event,首先來看一下它的結構:
1 typedef union epoll_data { 2 void *ptr; 3 int fd; 4 __uint32_t u32; 5 __uint64_t u64; 6 } epoll_data_t; 7 8 struct epoll_event { 9 __uint32_t events; /* Epoll events */ 10 epoll_data_t data; /* User data variable */ 11 };
可以看到,epoll_event結構體中有兩個成員,data成員存放的就是函數第三個參數值fd(不知道為什么需要重復),而events成員表示你想在這個fd上監聽什么樣的事件,它的取值可以是以下宏的集合:
1 EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉); 2 EPOLLOUT:表示對應的文件描述符可以寫; 3 EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來); 4 EPOLLERR:表示對應的文件描述符發生錯誤; 5 EPOLLHUP:表示對應的文件描述符被掛斷; 6 EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。 7 EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里
目前只用到了EPOLLIN和EPOLLOUT,剩下的宏還沒有仔細研究過。單純使用EPOLLIN和EPOLLOUT的話感覺就和在select函數中設置第二個和第三個參數一樣。
- int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout);
介紹完epoll_ctl(),再來看epoll_wait()就很好理解了,第一個參數依然是epoll_create()的返回值,表示你想對內核中的哪個文件描述符集合進行操作。
第二、第三個參數是一起的,第二個參數其實是一個epoll_event結構體的數組指針,maxevents是這個數組的大小(不能超過epoll_create()中參數size的大小),內核會把滿足條件的文件描述符相應的event信息存到這個數組中,然后用戶就可以從這個數組中讀取就緒的文件描述符並進行后續操作。
最后一個參數timeout和select的最后一個參數一樣,可以用來設置該操作的超時時間(阻塞或非阻塞的)。
該函數的返回值:
1 -1 : 出錯 2 0 : 超時 3 other: 滿足條件的fd個數
(未完)