socket 編程篇六之IPO多路復用-select poll epoll

http://blog.csdn.net/woxiaohahaa/article/details/51498951

文章參考自:http://blog.csdn.net/tennysonsky/article/details/45745887(秋葉原 — Mike VS 麥克《Linux系統編程——I/O多路復用select、poll、epoll的區別使用》)

此外,還有一篇好文:epoll機制:epoll_create、epoll_ctl、epoll_wait、close(魚思故淵的專欄)


在上一篇中,我簡單學習了 IO多路復用的基本概念,這里我將初學其三種實現手段:selectpollepoll


I/O 多路復用是為了解決進程或線程阻塞到某個 I/O 系統調用而出現的技術,使進程或線程不阻塞于某個特定的 I/O 系統調用。

select(),poll(),epoll()都是I/O多路復用的機制。I/O多路復用通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒,就是這個文件描述符進行讀寫操作之前),能夠通知程序進行相應的讀寫操作。但select(),poll(),epoll()本質上都是同步I/O,因為他們都需要在讀寫事件就緒后自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。

與多線程(TPC(Thread Per Connection)模型)和多進程(典型的Apache模型(Process Per Connection,簡稱PPC))相比,I/O 多路復用的最大優勢是系統開銷小,系統不需要建立新的進程或者線程,也不必維護這些線程和進程。



select()的使用


所需頭文件:

[cpp]?view plaincopy
  1. #include?<sys/select.h>??


函數原型:

[cpp]?view plaincopy
  1. int?select(int?nfds,?fd_set?*readfds,?fd_set?*writefds,?fd_set?*exceptfds,?struct?timeval?*timeout);??


函數描述:

監視并等待多個文件描述符的屬性變化(可讀、可寫或錯誤異常)。select()函數監視的文件描述符分 3 類,分別是writefds、readfds、和 exceptfds。調用后 select() 函數會阻塞,直到有描述符就緒(有數據可讀、可寫、或者有錯誤異常),或者超時( timeout 指定等待時間),函數才返回。當 select()函數返回后,可以通過遍歷 fdset,來找到就緒的描述符。


參數描述:

nfds:?要監視的文件描述符的范圍,一般取監視的描述符數的最大值+1,如這里寫 10, 這樣的話,描述符 0,1, 2 …… 9 都會被監視,在 Linux 上最大值一般為1024。

readfd:?監視的可讀描述符集合,只要有文件描述符讀操作準備就緒,這個文件描述符就存儲到這。

writefds:?監視的可寫描述符集合。

exceptfds:?監視的錯誤異常描述符集合。


三個參數 readfds、writefds 和 exceptfds 指定我們要讓內核監測讀、寫和異常條件的描述字。如果不需要使用某一個的條件,就可以把它設為NULL 。


幾個較為重要的宏:

[cpp]?view plaincopy
  1. //清空集合??
  2. void?FD_ZERO(fd_set?*fdset);???
  3. ??
  4. //將一個給定的文件描述符加入集合之中??
  5. void?FD_SET(int?fd,?fd_set?*fdset);??
  6. ??
  7. //將一個給定的文件描述符從集合中刪除??
  8. void?FD_CLR(int?fd,?fd_set?*fdset);??
  9. ??
  10. //檢查集合中指定的文件描述符是否可以讀寫???
  11. int?FD_ISSET(int?fd,?fd_set?*fdset);???
  12. ??
  13. timeout:?超時時間,它告知內核等待所指定描述字中的任何一個就緒可花多少時間。其?timeval?結構用于指定這段時間的秒數和微秒數。??
  14. struct?timeval??
  15. {??
  16. time_t?tv_sec;???????/*?秒?*/??
  17. suseconds_t?tv_usec;?/*?微秒?*/??
  18. };??


三種可能的函數返回情況:

1)永遠等待下去:timeout 設置為空指針 NULL,且沒有一個描述符準備好。

2)等待固定時間timeout 設置為某個固定時間,在有一個描述符準備好時返回,如果時間到了,就算沒有文件描述符準備就緒,這個函數也會返回 0。

3)不等待(不阻塞):檢查描述字后立即返回,這稱為輪詢。為此,struct timeval變量的時間值指定為 0 秒 0 微秒,文件描述符屬性無變化返回 0,有變化返回準備好的描述符數量。


函數返回值:

成功:就緒描述符的數目(同時修改readfds、writefds 和 exceptfds 三個參數),超時返回 0;
出錯:-1。


下面用 Socket 舉例,兩個客戶端,其中一個每隔 5s 發一個固定的字符串到服務器,另一個采集終端的鍵盤輸入,將其發給服務器,一個服務器,使用 IO 多路復用處理這兩個客戶端。代碼如下:


服務器:

[cpp]?view plaincopy
  1. #include?<cstdio>??
  2. #include?<sys/select.h>??
  3. #include?<unistd.h>??
  4. #include?<stdlib.h>??
  5. #include?<cstring>??
  6. #include?<cassert>??
  7. #include?<sys/types.h>??
  8. #include?<sys/socket.h>??
  9. #include?<netinet/in.h>??
  10. #include?<arpa/inet.h>??
  11. ??
  12. const?int?BUFFER_SIZE?=?4096;??
  13. const?int?SERVER_PORT?=?2222;??
  14. ??
  15. inline?int?max(int?a,?int?b){?return?(a?>?b???:?a,?b);}??
  16. ??
  17. int?main()??
  18. {??
  19. ????int?server_socket;??
  20. ????char?buff1[BUFFER_SIZE];??
  21. ????char?buff2[BUFFER_SIZE];??
  22. ????fd_set?rfds;??
  23. ????struct?timeval?tv;??
  24. ????int?ret;??
  25. ????int?n;??
  26. ??
  27. ????server_socket?=?socket(AF_INET,?SOCK_STREAM,?0);??
  28. ????assert(server_socket?!=?-1);??
  29. ??
  30. ????struct?sockaddr_in?server_addr;??
  31. ????memset(&server_addr,?0,?sizeof(server_addr));??
  32. ????server_addr.sin_family?=?AF_INET;??
  33. ????server_addr.sin_port?=?htons(SERVER_PORT);??
  34. ????server_addr.sin_addr.s_addr?=?htonl(INADDR_ANY);??
  35. ??
  36. ????assert(bind(server_socket,?(struct?sockaddr?*)&server_addr,?sizeof(server_addr))?!=?-1);??
  37. ????assert(listen(server_socket,?5)?!=?-1);??
  38. ??????
  39. ????struct?sockaddr_in?client_addr1,?client_addr2;??
  40. ????socklen_t?client_addr_len?=?sizeof(struct?sockaddr_in);??
  41. ??????
  42. ????printf("waiting...\n");??
  43. ??
  44. ????//此處先建立兩個?TCP?連接??
  45. ????int?connfd1?=?accept(server_socket,?(struct?sockaddr*)&client_addr1,?&client_addr_len);??
  46. ????assert(connfd1?!=?-1);??
  47. ????printf("connect?from?%s:%d\n",?inet_ntoa(client_addr1.sin_addr),?ntohs(client_addr1.sin_port));??
  48. ????int?connfd2?=?accept(server_socket,?(struct?sockaddr*)&client_addr2,?&client_addr_len);??
  49. ????assert(connfd2?!=?-1);??
  50. ????printf("connect?from?%s:%d\n",?inet_ntoa(client_addr2.sin_addr),?ntohs(client_addr2.sin_port));??
  51. ??
  52. ????while(1)??
  53. ????{??
  54. ????????FD_ZERO(&rfds);??
  55. ????????FD_SET(connfd1,?&rfds);??
  56. ????????FD_SET(connfd2,?&rfds);??
  57. ??
  58. ????????tv.tv_sec?=?10;??
  59. ????????tv.tv_usec?=?0;??
  60. ??????????
  61. ????????printf("select...\n");??
  62. ????????ret?=?select(max(connfd1,?connfd2)?+?1,?&rfds,?NULL,?NULL,?NULL);??
  63. ????????//ret?=?select(max(connfd1,?connfd2)?+?1,?&rfds,?NULL,?NULL,?&tv);??
  64. ??????????
  65. ????????if(ret?==?-1)??
  66. ????????????perror("select()");??
  67. ????????else?if(ret?>?0)??
  68. ????????{??
  69. ????????????if(FD_ISSET(connfd1,?&rfds))??
  70. ????????????{?????
  71. ????????????????n?=?recv(connfd1,?buff1,?BUFFER_SIZE,?0);??
  72. ????????????????buff1[n]?=?'\0';????????????????????//注意手動添加字符串結束符??
  73. ????????????????printf("connfd1:?%s\n",?buff1);??
  74. ????????????}??
  75. ????????????if(FD_ISSET(connfd2,?&rfds))??
  76. ????????????{??
  77. ????????????????n?=?recv(connfd2,?buff2,?BUFFER_SIZE,?0);??
  78. ????????????????buff2[n]?=?'\0';????????????????????//注意手動添加字符串結束符??
  79. ????????????????printf("connfd2:?%s\n",?buff2);??
  80. ????????????}?????????
  81. ????????}??
  82. ????????else??
  83. ????????????printf("time?out\n");??
  84. ????}??
  85. ??
  86. ????return?0;??
  87. }??

客戶端1:

[cpp]?view plaincopy
  1. #include?<cstdio>??
  2. #include?<unistd.h>??
  3. #include?<stdlib.h>??
  4. #include?<cstring>??
  5. #include?<cassert>??
  6. #include?<sys/types.h>??
  7. #include?<sys/socket.h>??
  8. #include?<netinet/in.h>??
  9. #include?<arpa/inet.h>??
  10. ??
  11. const?int?BUFFER_SIZE?=?4096;??
  12. const?int?SERVER_PORT?=?2222;??
  13. ??
  14. int?main()??
  15. {??
  16. ????int?client_socket;??
  17. ????const?char?*server_ip?=?"127.0.0.1";??
  18. ????char?buffSend[BUFFER_SIZE]?=?"I'm?from?d.cpp";??
  19. ??
  20. ????client_socket?=?socket(AF_INET,?SOCK_STREAM,?0);??
  21. ????assert(client_socket?!=?-1);??
  22. ??
  23. ????struct?sockaddr_in?server_addr;??
  24. ????memset(&server_addr,?0,?sizeof(server_addr));??
  25. ????server_addr.sin_family?=?AF_INET;??
  26. ????server_addr.sin_port?=?htons(SERVER_PORT);??
  27. ????server_addr.sin_addr.s_addr?=?inet_addr(server_ip);??
  28. ??
  29. ????assert(connect(client_socket,?(struct?sockaddr?*)&server_addr,?sizeof(server_addr))?!=?-1);??
  30. ??????
  31. ????while(1)??
  32. ????{??
  33. ????????assert(send(client_socket,?buffSend,?strlen(buffSend),?0)?!=?-1);??
  34. ????????sleep(5);??
  35. ????}??
  36. ????close(client_socket);??
  37. ??
  38. ????return?0;??
  39. }??


客戶端2:

[cpp]?view plaincopy
  1. #include?<cstdio>??
  2. #include?<unistd.h>??
  3. #include?<stdlib.h>??
  4. #include?<cstring>??
  5. #include?<cassert>??
  6. #include?<sys/types.h>??
  7. #include?<sys/socket.h>??
  8. #include?<netinet/in.h>??
  9. #include?<arpa/inet.h>??
  10. ??
  11. const?int?BUFFER_SIZE?=?4096;??
  12. const?int?SERVER_PORT?=?2222;??
  13. ??
  14. int?main()??
  15. {??
  16. ????int?client_socket;??
  17. ????const?char?*server_ip?=?"127.0.0.1";??
  18. ????char?buffSend[BUFFER_SIZE];??
  19. ??
  20. ????client_socket?=?socket(AF_INET,?SOCK_STREAM,?0);??
  21. ????assert(client_socket?!=?-1);??
  22. ??
  23. ????struct?sockaddr_in?server_addr;??
  24. ????memset(&server_addr,?0,?sizeof(server_addr));??
  25. ????server_addr.sin_family?=?AF_INET;??
  26. ????server_addr.sin_port?=?htons(SERVER_PORT);??
  27. ????server_addr.sin_addr.s_addr?=?inet_addr(server_ip);??
  28. ??
  29. ????assert(connect(client_socket,?(struct?sockaddr?*)&server_addr,?sizeof(server_addr))?!=?-1);??
  30. ??????
  31. ????while(1)??
  32. ????{??
  33. ????????fgets(buffSend,?BUFFER_SIZE,?stdin);??
  34. ????????assert(send(client_socket,?buffSend,?strlen(buffSend),?0)?!=?-1);??
  35. ????}??
  36. ????close(client_socket);??
  37. ??
  38. ????return?0;??
  39. }??

以上三份代碼有缺陷,代碼沒有很好的結束方式,都是 while(1) 死循環,運行的結束需要用 Ctrl + c  ⊙﹏⊙






poll()的使用


select() 和 poll() 系統調用的本質一樣,前者在 BSD UNIX 中引入的,后者在 System V 中引入的。poll() 的機制與 select() 類似,與 select() 在本質上沒有多大差別,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,但是 poll() 沒有最大文件描述符數量的限制(但是數量過大后性能也是會下降)poll() 和 select() 同樣存在一個缺點就是,包含大量文件描述符的數組被整體復制于用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨著文件描述符數量的增加而線性增大。


所需頭文件:

[cpp]?view plaincopy
  1. #include?<poll.h>??

函數原型:

[cpp]?view plaincopy
  1. int?poll(struct?pollfd?*fds,?nfds_t?nfds,?int?timeout);??

函數描述:

監視并等待多個文件描述符的屬性變化。


函數參數:

1)fds:與 select() 使用三個 fd_set 的方式不同,poll() 使用一個 pollfd 的指針實現。一個 pollfd 結構體數組,其中包括了你想監視的文件描述符和事件, 事件由結構中事件域 events 來確定,調用后實際發生的事件將被填寫在結構體的 revents 域。


[cpp]?view plaincopy
  1. struct?pollfd{??
  2. int?fd;?????????/*?文件描述符?*/??
  3. short?events;???/*?等待的事件?*/??
  4. short?revents;??/*?實際發生了的事件?*/??
  5. };???

_1、fd:每一個 pollfd 結構體指定了一個被監視的文件描述符,可以傳遞多個結構體,指示 poll() 監視多個文件描述符。


_2、events:每個結構體的 events 域是監視該文件描述符的事件掩碼,由用戶來設置這個域。


_3、revents:revents 域是文件描述符的操作結果事件掩碼,內核在調用返回時設置這個域。events 域中請求的任何事件都可能在 revents 域中返回。


事件的掩碼取值如下:


POLLIN | POLLPRI 等價于 select() 的讀事件,POLLOUT | POLLWRBAND 等價于 select() 的寫事件。POLLIN 等價于 POLLRDNORM | POLLRDBAND,而 POLLOUT 則等價于 POLLWRNORM 。例如,要同時監視一個文件描述符是否可讀和可寫,我們可以設置 events 為 POLLIN | POLLOUT。

每個結構體的 events 域是由用戶來設置,告訴內核我們關注的是什么,而 revents 域是返回時內核設置的,以說明對該描述符發生了什么事件。

2)nfds:用來指定第一個參數數組元素個數。


3)timeout:?指定等待的毫秒數,無論 I/O 是否準備好,poll() 都會返回。





函數返回值:

成功時,poll() 返回結構體中 revents 域不為 0 的文件描述符個數,如果在超時前沒有任何事件發生,poll()返回 0;

失敗時,poll() 返回 -1。



此處我們將上面的例子用 poll() 重新實現如下,只用修改服務器端代碼:

[cpp]?view plaincopy
  1. #include?<cstdio>??
  2. #include?<poll.h>??
  3. #include?<unistd.h>??
  4. #include?<stdlib.h>??
  5. #include?<cstring>??
  6. #include?<cassert>??
  7. #include?<sys/types.h>??
  8. #include?<sys/socket.h>??
  9. #include?<netinet/in.h>??
  10. #include?<arpa/inet.h>??
  11. ??
  12. const?int?BUFFER_SIZE?=?4096;??
  13. const?int?SERVER_PORT?=?2222;??
  14. ??
  15. int?main()??
  16. {??
  17. ????int?server_socket;??
  18. ????char?buff1[BUFFER_SIZE];??
  19. ????char?buff2[BUFFER_SIZE];??
  20. ????struct?timeval?tv;??
  21. ????int?ret;??
  22. ????int?n;??
  23. ??
  24. ????server_socket?=?socket(AF_INET,?SOCK_STREAM,?0);??
  25. ????assert(server_socket?!=?-1);??
  26. ??
  27. ????struct?sockaddr_in?server_addr;??
  28. ????memset(&server_addr,?0,?sizeof(server_addr));??
  29. ????server_addr.sin_family?=?AF_INET;??
  30. ????server_addr.sin_port?=?htons(SERVER_PORT);??
  31. ????server_addr.sin_addr.s_addr?=?htonl(INADDR_ANY);??
  32. ??
  33. ????assert(bind(server_socket,?(struct?sockaddr?*)&server_addr,?sizeof(server_addr))?!=?-1);??
  34. ????assert(listen(server_socket,?5)?!=?-1);??
  35. ??????
  36. ????struct?sockaddr_in?client_addr1,?client_addr2;??
  37. ????socklen_t?client_addr_len?=?sizeof(struct?sockaddr_in);??
  38. ??????
  39. ????printf("waiting...\n");??
  40. ??
  41. ????int?connfd1?=?accept(server_socket,?(struct?sockaddr*)&client_addr1,?&client_addr_len);??
  42. ????assert(connfd1?!=?-1);??
  43. ????printf("connect?from?%s:%d\n",?inet_ntoa(client_addr1.sin_addr),?ntohs(client_addr1.sin_port));??
  44. ????int?connfd2?=?accept(server_socket,?(struct?sockaddr*)&client_addr2,?&client_addr_len);??
  45. ????assert(connfd2?!=?-1);??
  46. ????printf("connect?from?%s:%d\n",?inet_ntoa(client_addr2.sin_addr),?ntohs(client_addr2.sin_port));??
  47. ??
  48. ????struct?pollfd?rfds[2];??
  49. ????rfds[0].fd?=?connfd1;??
  50. ????rfds[0].events?=?POLLIN;??
  51. ????rfds[1].fd?=?connfd2;??
  52. ????rfds[1].events?=?POLLIN;??
  53. ????tv.tv_sec?=?10;??
  54. ????tv.tv_usec?=?0;??
  55. ??????
  56. ????while(1)??
  57. ????{??
  58. ????????printf("poll...\n");??
  59. ????????ret?=?poll(rfds,?2,?-1);??
  60. ??????????
  61. ????????if(ret?==?-1)??
  62. ????????????perror("poll()");??
  63. ????????else?if(ret?>?0)??
  64. ????????{?????
  65. ????????????if((rfds[0].revents?&?POLLIN)?==?POLLIN)??
  66. ????????????{?????
  67. ????????????????n?=?recv(connfd1,?buff1,?BUFFER_SIZE,?0);??
  68. ????????????????buff1[n]?=?'\0';??
  69. ????????????????printf("connfd1:?%s\n",?buff1);??
  70. ????????????}??
  71. ????????????if((rfds[1].revents?&?POLLIN)?==?POLLIN)??
  72. ????????????{??
  73. ????????????????n?=?recv(connfd2,?buff2,?BUFFER_SIZE,?0);??
  74. ????????????????buff2[n]?=?'\0';??
  75. ????????????????printf("connfd2:?%s\n",?buff2);??
  76. ????????????}?????
  77. ????????}??
  78. ????????else??
  79. ????????????printf("time?out\n");??
  80. ????}??
  81. ??
  82. ????return?0;??
  83. }??







epoll()的使用



epoll 是在內核 2.6 中提出的,是之前的 select() 和 poll() 的增強版本。相對于 select() 和 poll() 來說,epoll 更加靈活,沒有描述符限制。epoll 使用一個文件描述符管理多個描述符,將用戶關心的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的 copy 只需一次。


epoll 操作過程需要三個接口,分別如下:

[cpp]?view plaincopy
  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);????



int epoll_create(int size);

功能:

該函數生成一個 epoll 專用的文件描述符。

參數:

size: 用來告訴內核這個監聽的數目一共有多大,參數 size 并不是限制了 epoll 所能監聽的描述符最大個數,只是對內核初始分配內部數據結構的一個建議。自從 linux 2.6.8 之后,size 參數是被忽略的,也就是說可以填只有大于 0 的任意值


返回值:
成功:epoll 專用的文件描述符
失敗:-1




int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

功能:

epoll 的事件注冊函數,它不同于 select() 是在監聽事件時告訴內核要監聽什么類型的事件,而是在這里先注冊要監聽的事件類型。

參數:

epfd:?epoll 專用的文件描述符,epoll_create()的返回值

op:?表示動作,用三個宏來表示:

EPOLL_CTL_ADD:注冊新的 fd 到 epfd 中;
EPOLL_CTL_MOD:修改已經注冊的fd的監聽事件;
EPOLL_CTL_DEL:從 epfd 中刪除一個 fd;

fd:?需要監聽的文件描述符

event:?告訴內核要監聽什么事件,struct epoll_event 結構如:

[cpp]?view plaincopy
  1. //?保存觸發事件的某個文件描述符相關的數據(與具體使用方式有關)????
  2. typedef?union?epoll_data?{????
  3. ????void?*ptr;????
  4. ????int?fd;????
  5. ????__uint32_t?u32;????
  6. ????__uint64_t?u64;????
  7. }?epoll_data_t;????
  8. ????
  9. //?感興趣的事件和被觸發的事件????
  10. struct?epoll_event?{????
  11. ????__uint32_t?events;?/*?Epoll?events?*/????
  12. ????epoll_data_t?data;?/*?User?data?variable?*/????
  13. };????

events 可以是以下幾個宏的集合:

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

返回值:

成功:0

失敗:-1



int epoll_wait( int epfd,?struct epoll_event * events,?int maxevents,?int timeout );

功能:

等待事件的產生,收集在 epoll 監控的事件中已經發送的事件,類似于 select() 調用。

參數:

epfd:?epoll 專用的文件描述符,epoll_create()的返回值

events:?分配好的 epoll_event 結構體數組,epoll 將會把發生的事件賦值到events 數組中(events 不可以是空指針,內核只負責把數據復制到這個 events 數組中,不會去幫助我們在用戶態中分配內存)。

maxevents:?maxevents 告之內核這個 events 有多少個 。

timeout:?超時時間,單位為毫秒,為 -1 時,函數為阻塞。

返回值:

成功:返回需要處理的事件數目,如返回 0 表示已超時

失敗:-1


epoll 對文件描述符的操作有兩種模式:LT(level trigger)和 ET(edge trigger)。LT 模式是默認模式LT 模式與 ET 模式的區別如下

LT 模式:支持block和no-block socket。當 epoll_wait 檢測到描述符事件發生并將此事件通知應用程序,應用程序可以不立即處理該事件。下次調用 epoll_wait 時,會再次響應應用程序并通知此事件。效率會低于ET觸發,尤其在大并發,大流量的情況下。但是LT對代碼編寫要求比較低,不容易出現問題。LT模式服務編寫上的表現是:只要有數據沒有被獲取,內核就不斷通知你,因此不用擔心事件丟失的情況。

ET 模式:只支持no-block socket。當 epoll_wait 檢測到描述符事件發生并將此事件通知應用程序,應用程序必須立即處理該事件。如果不處理,下次調用 epoll_wait 時,不會再次響應應用程序并通知此事件。該模式效率非常高,尤其在高并發,大流量的情況下,會比LT少很多epoll的系統調用。但是對編程要求高,需要細致的處理每個請求,否則容易發生丟失事件的情況。


接下來,我們將上面的例子,改為用 epoll 實現:

[cpp]?view plaincopy
  1. #include?<cstdio>??
  2. #include?<sys/epoll.h>??
  3. #include?<unistd.h>??
  4. #include?<stdlib.h>??
  5. #include?<cstring>??
  6. #include?<cassert>??
  7. #include?<sys/types.h>??
  8. #include?<sys/socket.h>??
  9. #include?<netinet/in.h>??
  10. #include?<arpa/inet.h>??
  11. ??
  12. const?int?BUFFER_SIZE?=?4096;??
  13. const?int?SERVER_PORT?=?2222;??
  14. ??
  15. int?main()??
  16. {??
  17. ????int?server_socket;??
  18. ????char?buff1[BUFFER_SIZE];??
  19. ????char?buff2[BUFFER_SIZE];??
  20. ????struct?timeval?tv;??
  21. ????int?ret;??
  22. ????int?n,?i;??
  23. ??
  24. ????server_socket?=?socket(AF_INET,?SOCK_STREAM,?0);??
  25. ????assert(server_socket?!=?-1);??
  26. ??
  27. ????struct?sockaddr_in?server_addr;??
  28. ????memset(&server_addr,?0,?sizeof(server_addr));??
  29. ????server_addr.sin_family?=?AF_INET;??
  30. ????server_addr.sin_port?=?htons(SERVER_PORT);??
  31. ????server_addr.sin_addr.s_addr?=?htonl(INADDR_ANY);??
  32. ??
  33. ????assert(bind(server_socket,?(struct?sockaddr?*)&server_addr,?sizeof(server_addr))?!=?-1);??
  34. ????assert(listen(server_socket,?5)?!=?-1);??
  35. ??????
  36. ????struct?sockaddr_in?client_addr1,?client_addr2;??
  37. ????socklen_t?client_addr_len?=?sizeof(struct?sockaddr_in);??
  38. ??????
  39. ????printf("waiting...\n");??
  40. ??
  41. ????int?connfd1?=?accept(server_socket,?(struct?sockaddr*)&client_addr1,?&client_addr_len);??
  42. ????assert(connfd1?!=?-1);??
  43. ????printf("connect?from?%s:%d\n",?inet_ntoa(client_addr1.sin_addr),?ntohs(client_addr1.sin_port));??
  44. ????int?connfd2?=?accept(server_socket,?(struct?sockaddr*)&client_addr2,?&client_addr_len);??
  45. ????assert(connfd2?!=?-1);??
  46. ????printf("connect?from?%s:%d\n",?inet_ntoa(client_addr2.sin_addr),?ntohs(client_addr2.sin_port));??
  47. ??
  48. ????tv.tv_sec?=?10;??
  49. ????tv.tv_usec?=?0;??
  50. ??????
  51. ????struct?epoll_event?event;??
  52. ????struct?epoll_event?wait_event[2];??
  53. ??????
  54. ????int?epfd?=?epoll_create(10);??
  55. ????assert(epfd?!=?-1);??
  56. ??????
  57. ????event.data.fd?=?connfd1;??
  58. ????event.events?=?EPOLLIN;??
  59. ????assert(epoll_ctl(epfd,?EPOLL_CTL_ADD,?connfd1,?&event)?!=?-1);??
  60. ????event.data.fd?=?connfd2;??
  61. ????event.events?=?EPOLLIN;??
  62. ????assert(epoll_ctl(epfd,?EPOLL_CTL_ADD,?connfd2,?&event)?!=?-1);??
  63. ??
  64. ??????
  65. ????while(1)??
  66. ????{??
  67. ????????printf("epoll...\n");??
  68. ????????ret?=?epoll_wait(epfd,?wait_event,?2,?-1);??
  69. ??????????
  70. ????????if(ret?==?-1)??
  71. ????????????perror("epoll()");??
  72. ????????else?if(ret?>?0)??
  73. ????????{?????
  74. ????????????for(i?=?0;?i?<?ret;?++i)??
  75. ????????????{??
  76. ????????????????if(wait_event[i].data.fd?==?connfd1?&&?(wait_event[i].events?&?EPOLLIN)?==?EPOLLIN)??
  77. ????????????????{?????
  78. ????????????????????n?=?recv(connfd1,?buff1,?BUFFER_SIZE,?0);??
  79. ????????????????????buff1[n]?=?'\0';??
  80. ????????????????????printf("connfd1:?%s\n",?buff1);??
  81. ????????????????}??
  82. ????????????????else?if(wait_event[i].data.fd?==?connfd2?&&?(wait_event[i].events?&?EPOLLIN)?==?EPOLLIN)??
  83. ????????????????{??
  84. ????????????????????n?=?recv(connfd2,?buff2,?BUFFER_SIZE,?0);??
  85. ????????????????????buff2[n]?=?'\0';??
  86. ????????????????????printf("connfd2:?%s\n",?buff2);??
  87. ????????????????}?????
  88. ????????????}??
  89. ????????}??
  90. ????????else??
  91. ????????????printf("time?out\n");??
  92. ????}??
  93. ??
  94. ????return?0;??
  95. }??


在 select/poll中,進程只有在調用一定的方法后,內核才對所有監視的文件描述符進行掃描,而 epoll() 事先通過 epoll_ctl() 來注冊一個文件描述符,一旦某個文件描述符就緒時,內核會采用類似 callback 的回調機制(軟件中斷 ),迅速激活這個文件描述符,當進程調用 epoll_wait() 時便得到通知。





下面分析 select、poll、epoll之間的優缺點:


select:

缺點:

1)每次調用select,都存在 fd 集合在用戶態與內核態之間的拷貝,I/O 的效率會隨著監視 fd 的數量的增長而線性下降。
2)select()調用的內部,需要用輪詢的方式去完整遍歷每一個 fd,如果遍歷完所有 fd 后沒有發現就緒 fd,則掛起當前進程,直到有 fd 就緒或者主動超時(使用 schedule_timeout 實現睡一會兒,判斷一次(被定時器喚醒,注意與 select() 函數里面的 timeout 參數區分作用)的效果),被喚醒后它又要再次遍歷 fd (直到有 fd 就緒或 select() 函數超時)。這個過程經歷了多次無謂的遍歷。CPU的消耗會隨著監視 fd 的數量的增長而線性增加

[cpp]?view plaincopy
  1. 步驟總結如下:??
  2. 1)先把全部fd掃一遍;??
  3. 2)如果發現有可用的fd,跳到5;??
  4. 3)如果沒有,當前進程去睡覺xx秒(schedule_timeout機制);??
  5. 4)xx秒后自己醒了,或者狀態變化的fd喚醒了自己,跳到1;??
  6. 5)結束循環體,返回。(注意函數的返回也可能是?timeout?的超時)??

3)select支持的文件描述符數量太小了,默認是1024。

4)由于 select 參數輸入和輸出使用同樣的 fd_set ,導致每次 select() ?之前都要重新初始化要監視的 fd_set,開銷也會比較大。


poll:

poll 的實現和 select 非常相似,它同樣存在 fd 集合在用戶態和內核態間的拷貝,且在函數內部需要輪詢整個 fd 集合。區別于select 的只是描述fd集合的方式不同,poll使用pollfd數組而不是select的fd_set結構,所以poll克服了select文件描述符數量的限制,此外,poll 的 polldf 結構體中分別用 events 和 revents 來存儲輸入和輸出,較之 select() 不用每次調用 poll() 之前都要重新初始化需要監視的事件。



epoll:

epoll是一種 Reactor 模式,提供了三個函數,epoll_create(),epoll_ctl() 和 epoll_wait()。

優點:

1)對于上面的第一個缺點,epoll 的解決方案在 epoll_ctl() 函數中。每次注冊新的事件到 epoll 描述符中時,會把該 fd 拷貝進內核,而不是在epoll_wait的時候重復拷貝。epoll 保證了每個fd在整個過程中只會拷貝一次。
2)對于第二個缺點,epoll 的解決方案不像 select 或 poll 一樣輪詢 fd,而只在 epoll_ctl 時把要監控的 fd 掛一遍,并為每個 fd 指定一個回調函數,當設備就緒,這個回調函數把就緒的 fd 加入一個就緒鏈表。epoll_wait 的工作實際上就是在這個就緒鏈表中查看有沒有就緒的 fd,也即 epoll_wait 只關心“活躍”的描述符,而不用像 select() 和 poll() 需要遍歷所有 fd,它需要不斷輪詢就緒鏈表,期間也可能多次睡眠和喚醒(類似與 select, poll),但終究它的輪詢只用判斷就續表是否為空即可,其CPU的消耗不會隨著監視 fd 的數量的增長而線性增加,這就是回調機制的優勢,也正是 epoll 的魅力所在。


同理,select() 和 poll() 函數返回后, 處理就緒 fd 的方法還是輪詢,如下:

[cpp]?view plaincopy
  1. int?res?=?select(maxfd+1,?&readfds,?NULL,?NULL,?120);????
  2. if?(res?>?0)????
  3. {????
  4. ????for?(int?i?=?0;?i?<?MAX_CONNECTION;?i++)????
  5. ????{????
  6. ????????if?(FD_ISSET(allConnection[i],?&readfds))????
  7. ????????{????
  8. ????????????handleEvent(allConnection[i]);????
  9. ????????}????
  10. ????}????
  11. }????
  12. //?if(res?==?0)?handle?timeout,?res?<?0?handle?error???


而 epoll() 只需要從就緒鏈表中處理就緒的 fd:

[cpp]?view plaincopy
  1. int?res?=?epoll_wait(epfd,?events,?20,?-1);????
  2. for?(int?i?=?0;?i?<?res;i++)????
  3. {????
  4. ????handleEvent(events[n]);????
  5. }????

此處的效率對比也是高下立判。




3)對于第三個缺點,epoll沒有這個限制,它所支持的FD上限是最大可以打開文件的數目,這個和系統限制有關,linux里面可以用ulimit查看文件打開數限制。


缺點:epoll是 linux 特有的,而 select 和 poll 是在 POSIX 中規定的,跨平臺支持更好。





綜上:

select 、poll、epoll 的使用要根據具體的使用場合,并不是 epoll 的性能就一定好,因為回調函數也是有消耗的,當 socket 連接較少時或者是即使 socket 連接很多,但是連接基本都是活躍的情況下,select / poll 的性能與 epoll 是差不多的。即如果沒有大量的 idle-connection 或者 dead-connection,epoll 的效率并不會比 select/poll 高很多,但是當遇到大量的 idle-connection,就會發現epoll 的效率大大高于 select/poll。


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

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

相關文章

UVa11054

挺簡單的小模擬。 還是要注意思維的方向&#xff0c;要有切入點&#xff0c;不能像無頭蒼蠅一樣東想一下西想一下&#xff0c;而應該分析問題的性質&#xff0c;然后嘗試思維的方向&#xff0c;從不同的方向思考&#xff08;順序&#xff0c;逆序&#xff0c;排序后&#xff0…

淺談傳輸層

1. 傳輸層的作用 在傳輸層中有兩個特別重要的協議 TCP/UDP . 以快遞員送快遞為例說明這個問題吧. 在進行包裹傳輸的過程中快遞員需要根據快遞上的目的地址(目的計算機)來投遞包裹(IP數據報), 加入在快遞單上只寫了收件人的所在地, 所在單位, 而只寫了收件人的姓沒有寫收件人的…

UVa10375

題目描述很簡單,就是求兩個組合數的商.可是數字范圍很大,肯定不能直接計算. 因此要用到唯一分解定理,即將結果全部表示為素因子的冪的形式. #include<cstdio> #include<cstring> #include<algorithm> #include<climits> #include<cctype> #inc…

I/O復用的 select poll和epoll的簡單實現

http://www.cnblogs.com/wj9012/p/3876734.html 一個tcp的客戶端服務器程序 服務器端不變&#xff0c;客戶端通過I/O復用輪詢鍵盤輸入與socket輸入&#xff08;接收客戶端的信息&#xff09; 服務器端&#xff1a; 1 /*服務器:2 1.客戶端關閉后&#xff0c;服務器再向客戶端發送…

netstat 相關命令解析

1.列出所有的端口 netstat -a 列出TCP協議的端口 netstat -at UDP協議的端口 netstat -au 2.列出處于監聽狀態的socket netstat -l 列出監聽的TCP端口 netstat -lt 列出監聽的UDP端口 netstat -lu 列出監聽的UNIX端口 netstat -lx 3.列出協議的統計信息 nestat …

UVa10791

我們可以先用唯一分解定理將這個數字分解成素因子冪的乘積&#xff0c;為了得到最小的和&#xff0c;我們可以發現&#xff1a;每個 素因子的冪單獨分開的和是最小的。 先說明每個素因子都是以出現的最大的次數出現。因為最小公倍數一定&#xff0c;因此至少有一個數字的這個素…

TCP相關代碼

TCP 基礎代碼 //tcp_server.c #include<stdio.h> #include<error.h> #include<sys/types.h> #include<string.h> #include<unistd.h> #include<sys/socket.h> #include<netinet/in.h> #include <arpa/inet.h> #include<st…

UVa1635

我們很容易發現最后每一項的系數就是二項式展開&#xff0c;余數和m沒有關系就意味著可以被m整除&#xff0c;因此我們就需要求出每一個二項式的系數。但是數據實在太大我們根據唯一分解定理將m和系數都進行分解&#xff0c;然后比較因子的冪。 二項式的計算我們可以根據楊輝三…

幾種并發服務器模型的實現:多線程,多進程,select,poll,epoll

http://www.cnblogs.com/wj9012/p/3879605.html 客戶端使用select模型&#xff1a; 1 #include <stdio.h>2 #include <stdlib.h>3 #include <string.h>4 #include <errno.h>5 #include <sys/types.h>6 #include <sys/socket.h>7 #include …

哈希表1

1. 初始化 void HashInit(HashTable* ht, HashFunc func) {if(ht NULL || func NULL){return;}ht -> size 0;ht -> func func;int i 0;for(; i < HashMaxSize; i){ht -> data[i].state Empty;} } 2. 哈希表的銷毀 void HashDestroy(HashTable* ht) {if(ht…

UVa10820

實質上就是求歐拉函數值 書上有個板子挺好&#xff0c;也不難理解。 #include<cstdio> #include<cstring> #include<algorithm> #include<climits> #include<cctype> #include<queue> #include<set>using namespace std;typedef l…

linux socket 編程(C語言)

https://www.cnblogs.com/x_wukong/p/4541010.html 最近看了一些網絡編程的書籍&#xff0c;一直以來總感覺網絡編程神秘莫測&#xff0c;其實網絡編程入門還是很容易學的&#xff0c;下面這些代碼是我在linux下編寫的&#xff0c;已經運行過了&#xff0c;編譯之后就可以運行了…

哈希表2

哈希表的初始化 void HashInit(HashTable* ht, HashFunc func) {if(ht NULL){return;}ht -> size 0;ht -> func func;size_t i 0;for(; i < MaxSize; i){ht -> data[i] NULL;} } 哈希表的結點創建 HashElem* CreateHashElemNode(KeyType key, ValueType va…

位圖

相關數據結構 typedef uint64_t BitmapType;#define BITMAPMAXSIZE 1000 //位圖所能容納的位數typedef struct Bitmap {uint64_t* data;uint64_t capacity; }Bitmap; 初始化 void BitmapInit(Bitmap* bm, uint64_t capacity) {if(bm NULL){return;}//當capacity 100, 2個元…

C++中的inline用法

https://www.cnblogs.com/fnlingnzb-learner/p/6423917.html 1. 引入inline關鍵字的原因 在c/c中&#xff0c;為了解決一些頻繁調用的小函數大量消耗棧空間&#xff08;棧內存&#xff09;的問題&#xff0c;特別的引入了inline修飾符&#xff0c;表示為內聯函數。 棧空間就是指…

UVa1262

算是一個模擬吧 #include<cstdio> #include<cstring> #include<algorithm> #include<climits> #include<cctype> #include<queue> #include<set> #include<vector>using namespace std;typedef long long ll; const int INF…

一個Linux下C線程池的實現

http://blog.csdn.net/zouxinfox/article/details/3560891 什么時候需要創建線程池呢&#xff1f;簡單的說&#xff0c;如果一個應用需要頻繁的創建和銷毀線程&#xff0c;而任務執行的時間又非常短&#xff0c;這樣線程創建和銷毀的帶來的開銷就不容忽視&#xff0c;這時也是線…

Gym100917 A - Abstract Picture

模擬賽的時候看這道題沒有什么頭緒&#xff0c;當時有點暈&#xff0c;感冒還沒有好&#xff0c;回來以后瞟了一眼題解就明白了&#xff0c;自己實現了一下&#xff0c;也沒有很復雜。大概的思路就像拓撲排序一樣&#xff0c;需要理解因為涂的是有順序的&#xff0c;所以我們總…

linux進程通信---幾個發送信號的函數(kill,raise,alarm,pause)

http://blog.csdn.net/zzyoucan/article/details/9235685 信號&#xff1a;信號是unix中最古老的進程通信的一種方式&#xff0c;他是軟件層次上對中斷機制的模擬&#xff0c;是一種異步通信方式&#xff0c;信號可以實現用戶空間進程和內核空間進程的交互&#xff0c;內核進程…

數據庫以及表的基本操作

一.數據庫的操作 create database[if not exists]數據庫名; 創建一個名字為company2的使用utf8忽略大小寫的數據庫 create database company charsetutf8 collate utf8_general_ci; 創建一個數據庫區分大小寫 create database company1 charsetutf8 collate utf8_general_bin;…