1. 程式人生 > >Linux網絡編程之epoll知識點備忘

Linux網絡編程之epoll知識點備忘

err timeout bind AS log 函數 而不是 新的 sizeof

首先是關於IO多路復用的基礎概念:

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

關鍵要了解阻塞非阻塞、同步異步之間的關系與區別,然後對於常用的IO多路復用方法要有所了解。

epoll是Linux獨有的IO多路復用技術,也是較新的方法,和傳統的select/poll方法相比主要優勢可以這樣理解:

select的幾大缺點:

(1)每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大

(2)同時每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大

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

poll和select基本相似,只不過交換的數據結構有所差別罷了。

epoll的特色:

epoll既然是對select和poll的改進,就應該能避免上述的三個缺點。epoll提供了三個函數,epoll_create,epoll_ctl和epoll_wait,epoll_create是創建一個epoll句柄;epoll_ctl是註冊要監聽的事件類型;epoll_wait則是等待事件的產生。

  對於第一個缺點,epoll的解決方案在epoll_ctl函數中。每次註冊新的事件到epoll句柄中時(在epoll_ctl中指定EPOLL_CTL_ADD),會把所有的fd拷貝進內核,而不是在epoll_wait的時候重復拷貝。epoll保證了每個fd在整個過程中只會拷貝一次。

  對於第二個缺點,epoll的解決方案不像select或poll一樣每次都把current輪流加入fd對應的設備等待隊列中,而只在epoll_ctl時把current掛一遍(這一遍必不可少)並為每個fd指定一個回調函數,當設備就緒,喚醒等待隊列上的等待者時,就會調用這個回調函數,而這個回調函數會把就緒的fd加入一個就緒鏈表)。epoll_wait的工作實際上就是在這個就緒鏈表中查看有沒有就緒的fd(利用schedule_timeout()實現睡一會,判斷一會的效果,和select實現中的第7步是類似的)。

  對於第三個缺點,epoll沒有這個限制,它所支持的FD上限是最大可以打開文件的數目,這個數字一般遠大於2048,舉個例子,在1GB內存的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統內存關系很大。

總結:

(1)select,poll實現需要自己不斷輪詢所有fd集合,直到設備就緒,期間可能要睡眠和喚醒多次交替。而epoll其實也需要調用epoll_wait不斷輪詢就緒鏈表,期間也可能多次睡眠和喚醒交替,但是它是設備就緒時,調用回調函數,把就緒fd放入就緒鏈表中,並喚醒在epoll_wait中進入睡眠的進程。雖然都要睡眠和交替,但是select和poll在“醒著”的時候要遍歷整個fd集合,而epoll在“醒著”的時候只要判斷一下就緒鏈表是否為空就行了,這節省了大量的CPU時間。這就是回調機制帶來的性能提升。

(2)select,poll每次調用都要把fd集合從用戶態往內核態拷貝一次,並且要把current往設備等待隊列中掛一次,而epoll只要一次拷貝,而且把current往等待隊列上掛也只掛一次(在epoll_wait的開始,註意這裏的等待隊列並不是設備等待隊列,只是一個epoll內部定義的等待隊列)。這也能節省不少的開銷。

通過對於epoll進一步的了解,我個人覺得要理解epoll最關鍵的是需要了解以下幾點:

1.和select等的比較。主要從效率,方法,監聽數量等方面需要有一個比較全面的了解

2.關於epoll的三個函數需要了解用法

3.對於epoll_event這個數據結構需要有深刻了解,特別是其中的epoll_data數據,其中的union可以保存許多類型的用戶數據,這為回調函數等提供了途徑

最後摘抄一點代碼用於提示epoll的基本使用方法:

 1 int main()
 2 {
 3   int i, maxi, listenfd, new_fd, sockfd,epfd,nfds;
 4   ssize_t n;
 5   char line[MAXLINE];
 6   socklen_t clilen;
 7   struct epoll_event ev,events[20];//ev用於註冊事件,數組用於回傳要處理的事件
 8   struct sockaddr_in clientaddr, serveraddr;
 9   listenfd = socket(AF_INET, SOCK_STREAM, 0);//生成socket文件描述符
10   setnonblocking(listenfd);//把socket設置為非阻塞方式
11   epfd=epoll_create(256);//生成用於處理accept的epoll專用的文件描述符
12   ev.data.fd=listenfd;//設置與要處理的事件相關的文件描述符
13   ev.events=EPOLLIN|EPOLLET;//設置要處理的事件類型
14   epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);//註冊epoll事件
15     //設置服務器端地址信息
16   bzero(&serveraddr, sizeof(serveraddr));
17   serveraddr.sin_family = AF_INET;
18   char *local_addr= LOCAL_ADDR;
19   inet_aton(local_addr,&(serveraddr.sin_addr));
20   serveraddr.sin_port=htons(SERV_PORT);
21   bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));//綁定socket連接
22   listen(listenfd, LISTENQ);//監聽
23   maxi = 0;
24   for ( ; ; )
25       {
26          /* epoll_wait:等待epoll事件的發生,並將發生的sokct fd和事件類型放入到events數組中;
27           * nfds:為發生的事件的個數。
28           * 註:
29          */
30       nfds=epoll_wait(epfd,events,20,500);
31       //處理所發生的所有事件
32       for(i=0;i<nfds;++i)
33       {
34           if(events[i].data.fd==listenfd)//事件發生在listenfd上
35           {
36                /* 獲取發生事件端口信息,存於clientaddr中;
37                *new_fd:返回的新的socket描述符,用它來對該事件進行recv/send操作*/
38               new_fd = accept(listenfd,(struct sockaddr *)&clientaddr, &clilen);
39               if(new_fd<0)
40                    {
41                   perror("new_fd<0");
42                   exit(1);
43               }
44               setnonblocking(new_fd);
45               char *str = inet_ntoa(clientaddr.sin_addr);
46               ev.data.fd=new_fd;//設置用於讀操作的文件描述符
47               ev.events=EPOLLIN|EPOLLET;//設置用於註測的讀操作事件
48               epoll_ctl(epfd,,,&ev);//註冊ev
49           }
50           else if(events[i].events&EPOLLIN)
51           {
52               if ( (sockfd = events[i].data.fd) < 0)
53                        continue;
54               if ( (n = read(sockfd, line, MAXLINE)) < 0)
55                    {
56                   if (errno == ECONNRESET)
57                       {
58                       close(sockfd);
59                       events[i].data.fd = -1;
60                   }
61                       else
62                       std::cout<<"readline error"<<std::endl;
63               }
64                   else if (n == 0)
65                   {
66                   close(sockfd);
67                   events[i].data.fd = -1;
68              }
69              ev.data.fd=sockfd;//設置用於寫操作的文件描述符
70              ev.events=EPOLLOUT|EPOLLET;//設置用於註測的寫操作事件
71              epoll_ctl(epfd,,sockfd,&ev);//修改sockfd上要處理的事件為EPOLLOUT
72         }
73        else if(events[i].events&EPOLLOUT)
74        {
75            sockfd = events[i].data.fd;
76             write(sockfd, line, n);
77             ev.data.fd=sockfd;//設置用於讀操作的文件描述符
78             ev.events=EPOLLIN|EPOLLET;//設置用於註測的讀操作事件
79             epoll_ctl(epfd,,sockfd,&ev);//修改sockfd上要處理的事件為EPOLIN
80        }
81    }
82  }
83 }

參考:http://blog.51cto.com/7666425/1261446

   http://www.cnblogs.com/Anker/p/3265058.html

Linux網絡編程之epoll知識點備忘