epoll詳解

http://blog.csdn.net/majianfei1023/article/details/45772269

歡迎轉載,轉載請注明原文地址:http://blog.csdn.net/majianfei1023/article/details/45772269


一.基本概念:

1.epoll是什么:
epoll是Linux內核為處理大批量文件描述符而作了改進的poll,是Linux下多路復用IO接口select/poll的增強版本,它能顯著提高程序在大量并發連接中只有少量活躍的情況下的系統CPU利用率。另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被內核IO事件異步喚醒而加入就緒隊列(Ready)的描述符集合就行了。epoll除了提供select/poll那種IO事件的水平觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態,減少epoll_wait的調用,提高應用程序效率。

2.epoll,select,poll的區別:
select,poll,epoll都是IO多路復用的機制。I/O多路復用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。
select和poll都需要在返回后,通過遍歷文件描述符來獲取已經就緒的socket。事實上,同時連接的大量客戶端在一時刻可能只有很少的處于就緒狀態,因此隨著監視的描述符數量的增長,其效率也會線性下降。


select目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優點,事實上從現在看來,這也是它所剩不多的優點之一。
select的幾大缺點:
(1)單個進程能夠監視的文件描述符的數量存在最大限制,在Linux上一般為1024,不過可以通過修改宏定義甚至重新編譯內核的方式提升這一限制。但是隨著文件描述符數量的增大,其復制的開銷也線性增長。同時,由于網絡響應時間的延遲使得大量TCP連接處于非活躍狀態,但調用select()會對所有socket進行一次線性掃描,所以這也浪費了一定的開銷。
(2)每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大

(3)同時每次調用select都需要在內核進行線性遍歷,時間復雜度為O(N),這個開銷在fd很多時也很大

poll的缺點:
它和select在本質上沒有多大差別,但是poll沒有最大文件描述符數量的限制(也就是沒有select的缺點1,卻有它的缺點2和3)。
poll和select同樣存在一個缺點就是,包含大量文件描述符的數組被整體復制于用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨著文件描述符數量的增加而線性增大。


epoll的優點主要是以下幾個方面:
1. 監視的描述符數量不受限制,它所支持的FD上限是最大可以打開文件的數目,這個數字一般遠大于2048,舉個例子,在1GB內存的機器上大約是10萬左 右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統內存關系很大。 (我2G內存的機器上是239545)。select的最大缺點就是進程打開的fd是有數量限制的

2. IO的效率不會隨著監視fd的數量的增長而下降。epoll不同于select和poll輪詢的方式,而是通過每個fd定義的回調函數來實現的。只有就緒的fd才會執行回調函數,時間復雜度為O(1)


3. 支持電平觸發和邊沿觸發兩種方式(具體區別,看下面的2.2epoll詳解-工作模式),理論上邊緣觸發的性能要更高一些,但是代碼實現相當復雜。

4. mmap加速內核與用戶空間的信息傳遞。epoll是通過內核于用戶空間mmap同一塊內存,避免了無謂的內存拷貝。

通過表格來看一下更清楚。

select

poll

epoll

支持最大連接數

1024(2048)

無上限

無上限

IO效率

每次調用進行線性遍歷,時間復雜度為O(N)

每次調用進行線性遍歷,時間復雜度為O(N)

使用“事件”通知方式,每當fd就緒,系統注冊的回調函數就會被調用,將就緒fd放到rdllist里面,這樣epoll_wait返回的時候我們就拿到了就緒的fd。時間發復雜度O(1)

fd拷貝

每次select都拷貝

每次poll都拷貝

調用epoll_ctl時拷貝進內核并由內核保存,之后每次epoll_wait不拷貝





二.epoll詳解:
既然了解了epoll的基本概念和優點,那我們就來看看epoll怎么用。
1.epoll的接口:
epoll操作過程需要三個接口,分別如下:
[cpp]?view plain?copy
  1. #include?<sys/epoll.h>??
  2. int?epoll_create(int?size);??
  3. int?epoll_ctl(int?epfd,?int?op,?int?fd,?struct?epoll_event?*event);??
  4. int?epoll_wait(int?epfd,?struct?epoll_event?*?events,?int?maxevents,?int?timeout);??


(1)?int epoll_create(int size);
  創建一個epoll的句柄,size用來告訴內核這個監聽的數目一共有多大。在linux-2.4.32內核中根據size大小初始化哈希表的大小,在linux2.6.10內核中該參數無用,使用紅黑樹管理所有的文件描述符,而不是hash。需要注意的是,當創建好epoll句柄后,它就是會占用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll后,必須調用close()關閉,否則可能導致fd被耗盡。


(2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注冊函數,它不同于select()在監聽事件時告訴內核要監聽什么類型的事件,而是在這里先注冊要監聽的事件類型。
第一個參數epfd是epoll_create()的返回值;

第二個參數op表示動作,用三個宏來表示:

??EPOLL_CTL_ADD:注冊新的fd到epfd中,
??EPOLL_CTL_MOD:修改已經注冊的fd的監聽事件,
??EPOLL_CTL_DEL:從epfd中刪除一個fd;
第三個參數fd是需要監聽的fd;
第四個參數*event是告訴內核需要監聽什么事,struct epoll_event結構如下:

[cpp]?view plain?copy
  1. struct?epoll_event?{??
  2. ??__uint32_t?events;??/*?Epoll?events?*/??
  3. ??epoll_data_t?data;??/*?User?data?variable?*/??
  4. };??
  5. ??
  6. ??
  7. typedef?union?epoll_data?{????
  8. void?*ptr;????
  9. int?fd;????
  10. __uint32_t?u32;????
  11. __uint64_t?u64;????
  12. }?epoll_data_t;??

events可以是以下幾個宏的集合:
??EPOLLIN?:表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
??EPOLLOUT:表示對應的文件描述符可以寫;
??EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);
??EPOLLERR:表示對應的文件描述符發生錯誤;
??EPOLLHUP:表示對應的文件描述符被掛斷;
??EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對于水平觸發(Level Triggered)來說的。
??EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket(也就是第三個參數fd還是epoll_data.fd)的話,需要再次把這個socket加入到EPOLL隊列里

(3)?int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的產生,類似于select()調用。參數events用來從內核得到事件的集合,maxevents告之內核這個events有多大,這個maxevents的值不能大于創建epoll_create()時的size,參數timeout是超時時間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。該函數返回需要處理的事件數目,如返回0表示已超時。


epoll_wait運行的原理是
等侍注冊在epfd上的socket fd的事件的發生,如果發生則將發生的sokct fd和事件類型放入到events數組中。
并 且將注冊在epfd上的socket fd的事件類型給清空,所以如果下一個循環你還要關注這個socket fd的話,則需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)來重新設置socket fd的事件類型。這時不用EPOLL_CTL_ADD,因為socket fd并未清空,只是事件類型清空。這一步非常重要。


2.epoll工作模式:
EPOLL事件有兩種模型:
LT(level triggered-電平觸發)是缺省的工作方式,并且同時支持block和no-block socket.在這種做法中,內核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的fd進行IO操作。如果你不作任何操作或者未處理完,內核還是會繼續通知你的,所以,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表。
ET(edge triggered-邊緣觸發)是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變為就緒時,內核通過epoll告訴你。然后它會假設你知道文件描述符已經就緒,并且不會再為那個文件描述符發送更多的就緒通知,直到你做了某些操作導致那個文件描述符不再為就緒狀態了(比如,你在發送,接收或者接收請求,或者發送接收的數據少于一定量時導致了一個EWOULDBLOCK 錯誤),邊緣觸發的意思就是在兩個狀態的臨界值進行改變的時候觸發。但是請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核不會發送更多的通知(only once),不過在TCP協議中,ET模式的加速效用仍需要更多的benchmark確認。
? ET和LT的區別就在這里體現,LT事件不會丟棄,而是只要讀buffer里面有數據可以讓用戶讀,則不斷的通知你。而ET則只在事件發生之時通知。可以簡單理解為LT是水平觸發,而ET則為邊緣觸發。LT模式只要有事件未處理就會觸發,而ET則只在高低電平變換時(即狀態從1到0或者0到1)觸發。
? ET模式在很大程度上減少了epoll事件被重復觸發的次數,因此效率要比LT模式高。epoll工作在ET模式的時候,必須使用非阻塞套接口,以避免由于一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。


三.epoll的例子:

[cpp]?view plain?copy
  1. #include?<stdio.h>??
  2. #include?<unistd.h>??
  3. #include?<stdlib.h>??
  4. #include?<string.h>??
  5. #include?<sys/types.h>??
  6. #include?<errno.h>??
  7. #include?<sys/socket.h>??
  8. #include?<netinet/in.h>??
  9. #include?<sys/epoll.h>??
  10. #include?<fcntl.h>??
  11. ??
  12. #define?MAX_EPOLL???????1000??
  13. #define?BUF_SIZE????????1024??
  14. #define?PORT????????????6000??
  15. ??
  16. ??
  17. int?setnonblocking(?int?fd?)??
  18. {??
  19. ????if(?fcntl(?fd,?F_SETFL,?fcntl(?fd,?F_GETFD,?0?)|O_NONBLOCK?)?==?-1?)??
  20. ????{??
  21. ????????printf("Set?blocking?error?:?%d\n",?errno);??
  22. ????????return?-1;??
  23. ????}??
  24. ????return?0;??
  25. }??
  26. ??
  27. int?main(?int?argc,?char?**?argv?)??
  28. {??
  29. ????int?????????listen_fd;??
  30. ????int?????????conn_fd;??
  31. ????int?????????epoll_fd;??
  32. ????int?????????nread;??
  33. ????int?????i;??
  34. ????struct?sockaddr_in?servaddr;??
  35. ????struct?sockaddr_in?cliaddr;??
  36. ????struct??epoll_event?ev;??//監聽fd??
  37. ????struct??epoll_event?events[MAX_EPOLL];??
  38. ????char????buf[BUF_SIZE];??
  39. ????socklen_t???len?=?sizeof(?struct?sockaddr_in?);??
  40. ??????
  41. ????bzero(?&servaddr,?sizeof(?servaddr?)?);??
  42. ????servaddr.sin_family?=?AF_INET;??
  43. ????servaddr.sin_addr.s_addr?=?htonl(?INADDR_ANY?);??
  44. ????servaddr.sin_port?=?htons(?PORT?);??
  45. ??????
  46. ????if(?(?listen_fd?=?socket(?AF_INET,?SOCK_STREAM,?0?)?)?==?-1?)??
  47. ????{??
  48. ????????printf("socket?error...\n"?,?errno?);??
  49. ????????exit(?EXIT_FAILURE?);??
  50. ????}??
  51. ??????
  52. ????if(?setnonblocking(?listen_fd?)?==?-1?)??
  53. ????{??
  54. ????????printf("setnonblocking?error?:?%d\n",?errno);??
  55. ????????exit(?EXIT_FAILURE?);??
  56. ????}??
  57. ??????
  58. ????if(?bind(?listen_fd,?(?struct?sockaddr?*)&servaddr,?sizeof(?struct?sockaddr?)?)?==?-1?)??
  59. ????{??
  60. ????????printf("bind?error?:?%d\n",?errno);??
  61. ????????exit(?EXIT_FAILURE?);??
  62. ????}??
  63. ??
  64. ????if(?listen(?listen_fd,?10?)?==?-1?)??
  65. ????{??
  66. ????????printf("Listen?Error?:?%d\n",?errno);??
  67. ????????exit(?EXIT_FAILURE?);??
  68. ????}??
  69. ??????
  70. ????epoll_fd?=?epoll_create(?MAX_EPOLL?);???//1.epoll_create??
  71. ????ev.events?=?EPOLLIN?|?EPOLLET;??
  72. ????ev.data.fd?=?listen_fd;??
  73. ????if(?epoll_ctl(?epoll_fd,?EPOLL_CTL_ADD,?listen_fd,?&ev?)?<?0?)???????//2.epoll_ctl??
  74. ????{??
  75. ????????printf("Epoll?Error?:?%d\n",?errno);??
  76. ????????exit(?EXIT_FAILURE?);??
  77. ????}??
  78. ??????
  79. ????while(?1?)??
  80. ????{??
  81. ????????int?ready_counts?=?0;??
  82. ????????if(?(?ready_counts?=?epoll_wait(?epoll_fd,?events,?MAX_EPOLL,?-1?)?)?==?-1?)????????//3.epoll_wait,就緒的event在events里面??
  83. ????????{??
  84. ????????????printf(?"Epoll?Wait?Error?:?%d\n",?errno?);??
  85. ????????????exit(?EXIT_FAILURE?);??
  86. ????????}??
  87. ??
  88. ????????for(?i?=?0;?i?<?ready_counts;?i++?)??
  89. ????????{??
  90. ????????????if(?events[i].data.fd?==?listen_fd)?????//監聽端口有就緒事件??
  91. ????????????{??
  92. ????????????????if(?(?conn_fd?=?accept(?listen_fd,?(struct?sockaddr?*)&cliaddr,?&len?)?)?==?-1?)??
  93. ????????????????{??
  94. ????????????????????printf("Accept?Error?:?%d\n",?errno);??
  95. ????????????????????exit(?EXIT_FAILURE?);??
  96. ????????????????}??
  97. ??????????????????
  98. ????????????????printf(?"Server?get?from?client?!\n"/*,??inet_ntoa(cliaddr.sin_addr),?cliaddr.sin_port?*/);??
  99. ??????????????????
  100. ????????????????ev.events?=?EPOLLIN?|?EPOLLET;??
  101. ????????????????ev.data.fd?=?conn_fd;??
  102. ????????????????if(?epoll_ctl(?epoll_fd,?EPOLL_CTL_ADD,?conn_fd,?&ev?)?<?0?)??
  103. ????????????????{??
  104. ????????????????????printf("Epoll?Error?:?%d\n",?errno);??
  105. ????????????????????exit(?EXIT_FAILURE?);??
  106. ????????????????}??
  107. ????????????}??
  108. ????????????else??
  109. ????????????{??
  110. ????????????????nread?=?read(?events[i].data.fd,?buf,?sizeof(?buf?)?);??
  111. ????????????????if(?nread?<=?0?)??
  112. ????????????????{??
  113. ????????????????????close(?events[i].data.fd?);??
  114. ????????????????????epoll_ctl(?epoll_fd,?EPOLL_CTL_DEL,?events[i].data.fd,?&ev?);??
  115. ????????????????????continue;??
  116. ????????????????}??
  117. ??????????????????
  118. ????????????????write(?events[i].data.fd,?buf,?nread?);??
  119. ????????????}??
  120. ????????}??
  121. ????}??
  122. ??????
  123. ????close(?listen_fd?);??
  124. ????return?0;??
  125. }??

四:總結:

既然epoll有這么多優點,是不是可以取代select和poll。

什么情況下用select/poll而不用epoll呢?


1.epoll更適合于處理大量的fd ,且活躍fd不是很多的情況。
反之如果處理的fd量不大,且基本都是活躍的,epoll并不比select/poll有什么效率。相反,過多使用epoll_ctl,效率相比還有稍微的下降。這時候使用select既簡單又方便,就沒必要用epoll這么復雜的寫法了。
2.select目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優點


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383766.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383766.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383766.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數據分割-并查集+set

小w來到百度之星的賽場上&#xff0c;準備開始實現一個程序自動分析系統。 這個程序接受一些形如xixj 或 xi≠xj 的相等/不等約束條件作為輸入&#xff0c;判定是否可以通過給每個 w 賦適當的值&#xff0c;來滿足這些條件。 輸入包含多組數據。 然而粗心的小w不幸地把每組數據…

linux c++線程池的實現

http://blog.csdn.net/zhoubl668/article/details/8927090?t1473221020107 線程池的原理大家都知道&#xff0c;直接上代碼了^_^ Thread.h [cpp] view plaincopy #ifndef __THREAD_H #define __THREAD_H #include <vector> #include <string> #inc…

樹啟發式合并入門

所謂啟發式合并&#xff0c;就是一種符合直覺的合并方法&#xff1a;將小的子樹合并在大的子樹上。 這些問題一般是相似的問題背景&#xff1a;都是樹上的計數問題&#xff0c;都不能直接從上往下進行暴力&#xff0c;都需要從下往上計數時對子樹信息進行運算從而得到父親節點的…

鏈棧基本操作

http://blog.csdn.net/jwentao01/article/details/46765517###;棧基本概念&#xff1a; 棧&#xff08;stack&#xff09;是限定在表尾進行插入和刪除操作的線性表&#xff08;或單鏈表&#xff09;。 //只能在一段進行插入和刪除&#xff0c;因此不存在&#xff0c;在中間進行…

Linux網絡編程---I/O復用模型之select

https://blog.csdn.net/men_wen/article/details/53456435Linux網絡編程—I/O復用模型之select 1. IO復用模型 IO復用能夠預先告知內核&#xff0c;一旦發現進程指定的一個或者多個IO條件就緒&#xff0c;它就通知進程。IO復用阻塞在select或poll系統調用上&#xff0c;而不是阻…

UVa12633-Super Rooks on Chessboard-容斥+FFT

題目大意就是給你一個R*C的棋盤&#xff0c;上面有超級兵&#xff0c;這種超級兵會攻擊 同一行、同一列、同一主對角線的所有元素&#xff0c;現在給你N個超級兵的坐標&#xff0c;需要你求出有多少方塊是不能被攻擊到的(R,C,N<50000) 遇到這種計數問題就要聯想到容斥&#…

Linux網絡編程---I/O復用模型之poll

https://blog.csdn.net/men_wen/article/details/53456474Linux網絡編程—I/O復用模型之poll 1.函數poll poll系統調用和select類似&#xff0c;也是在指定時間內輪詢一定數量的文件描述符&#xff0c;以測試其中是否有就緒者。 #include <poll.h>int poll(struct pollfd…

FFT模板

整理了一下&#xff0c;自己寫了一下模板 const double PIacos(-1.0); struct complex {double r,i;complex(double _r0,double _i0):r(_r),i(_i){}complex operator (const complex &b) {return complex(rb.r,ib.i);}complex operator -(const complex &b) {return c…

Linux網絡編程---I/O復用模型之epoll

https://blog.csdn.net/men_wen/article/details/53456474 Linux網絡編程—I/O復用模型之epoll 1. epoll模型簡介 epoll是Linux多路服用IO接口select/poll的加強版&#xff0c;e對應的英文單詞就是enhancement&#xff0c;中文翻譯為增強&#xff0c;加強&#xff0c;提高&…

POJ 1741tree-點分治入門

學習了一下點分治&#xff0c;如果理解有誤還請不吝賜教。 為了快速求得樹上任意兩點之間距離滿足某種關系的點對數&#xff0c;我們需要用到這種算法。 點分治是樹上的一種分治算法&#xff0c;依靠樹和子樹之間的關系進行分治從而降低復雜度。 和其他樹上的算法有一些區別…

基于單鏈表的生產者消費者問題

『生產者與消費者問題分析』「原理」生產者生產產品&#xff0c;消費者消費產品。產品如果被消費者消費完了&#xff0c;同時生產者又沒有生產出產品&#xff0c;消費者 就必須等待。同樣的&#xff0c;如果生產者生產了產品&#xff0c;而消費者沒有去消費&#x…

C++智能指針(一)智能指針的簡單介紹

https://blog.csdn.net/nou_camp/article/details/70176949C智能指針 在正式了解智能指針前先看一下下面的一段代碼 #include<iostream> using namespace std; class A { public:A():_ptr(NULL), _a(0){}~A(){} public:int* _ptr;int _a; };void test() {A a;int *p1 ne…

聰聰可可-點分治

聰聰和可可是兄弟倆&#xff0c;他們倆經常為了一些瑣事打起來&#xff0c;例如家中只剩下最后一根冰棍而兩人都想吃、兩個人都想玩兒電腦&#xff08;可是他們家只有一臺電腦&#xff09;……遇到這種問題&#xff0c;一般情況下石頭剪刀布就好了&#xff0c;可是他們已經玩兒…

C++智能指針(二)模擬實現三種智能指針

https://blog.csdn.net/nou_camp/article/details/70186721在上一篇博客中提到了Auto_ptr(C智能指針&#xff08;一&#xff09;)&#xff0c;下面進行模擬實現Auto_ptr 采用類模板實現 #include<iostream> using namespace std; template<class T> class Autoptr …

Prime Distance On Tree-樹分治+FFT

題目描述 Problem description. You are given a tree. If we select 2 distinct nodes uniformly at random, what’s the probability that the distance between these 2 nodes is a prime number? Input The first line contains a number N: the number of nodes in this…

C++智能指針(三)總結

https://blog.csdn.net/nou_camp/article/details/70195795 在上一篇博客中&#xff08;C智能指針&#xff08;二&#xff09;&#xff09;模擬實現了三種智能指針。 其中最好的就是shared_ptr,但是這并不代表它就是最完美的&#xff0c;它也有問題&#xff0c;這個問題就是循環…

POJ2114-Boatherds-樹分治

題目描述 Boatherds Inc. is a sailing company operating in the country of Trabantustan and offering boat trips on Trabantian rivers. All the rivers originate somewhere in the mountains and on their way down to the lowlands they gradually join and finally th…

c++11 你需要知道這些就夠了

https://blog.csdn.net/tangliguantou/article/details/50549751c11新特性舉著火把尋找電燈今天我就權當拋磚引玉&#xff0c;如有不解大家一起探討。有部分內容是引用自互聯網上的內容&#xff0c;如有問題請聯系我。T&& 右值引用 std::move 右值引用出現之前我們只能…

HDU5977-Garden of Eden-樹分治+FWT

題目描述 When God made the first man, he put him on a beautiful garden, the Garden of Eden. Here Adam lived with all animals. God gave Adam eternal life. But Adam was lonely in the garden, so God made Eve. When Adam was asleep one night, God took a rib fro…

C++11新特性學習

https://blog.csdn.net/tennysonsky/article/details/778170481、什么是C11C11標準為C編程語言的第三個官方標準&#xff0c;正式名叫ISO/IEC 14882:2011 - Information technology -- Programming languages -- C。在正式標準發布前&#xff0c;原名C0x。它將取代C標準第二版I…