I/O多路轉接之epoll

一、對epoll的初步認識

    epoll是爲了處理大量句柄而對poll做了改進。

    epoll的相關係統調用:

    (1)iint epoll_create(int size)

            創建一個epoll的句柄。size通常是被忽略的。當創建epoll句柄後,它就會佔用一個fd值,所以在使用完epoll後,必須調用close()進行關閉,否則可能導致fd被耗盡。

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

            epoll的事件註冊函數,它不同於select()是在監視事件時告訴內核要監聽什麼類型的事件,而是在這裏先註冊要監聽的事件類型。

            第一個參數是epoll_create()的返回值;

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

            EPOLL_CTL_ADD:註冊新的fd到epfd中;

            EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;

            EPOLL_CTL_DEL:從epfd中刪除一個fd;

            第三個參數是需要監聽的fd。

            第四個參數是告訴內核需要監聽什麼事,struct epoll_event結構如下:

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

            EPOLLIN:表示對應的文件描述符可以讀。

            EPOLLOUT:表示對應的文件描述符可以寫。

            EPOLLPRI:表示對應的文件描述符有緊急的數據可讀。

            EPOLLERR:表示對應的文件描述符發生錯誤。

            EPOLLHUP:表示對應的文件描述符被掛斷。

            EPOLLLET:將EPOLL設爲邊緣觸發模式(ET)。這裏是相對於水平觸發(LT)來說的。

            EPOLLONESHOT:只監聽一次事件。當監聽完這個事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到epoll隊列中。

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

             收集在epoll監控的事件中已經發送的事件。參數events是分配好的epoll_event結構體數組。epoll將會把發生的事件賦值到events數組中(evevts不可以是空指針,內核只負責把數據複製到evevts數組中,並不會幫助我們在用戶態中分配內存)。maxevents告知內核這個events有多大,這個maxevents的值不能大於epoll_create()時的size。參數timeout是超時時間,如果函數調用成功,返回對應I/O已準備好的文件描述符數目,如果返回0表示已超時。

二、epoll的工作原理

        epoll同樣只告知那些就緒的文件描述符,而且當我們調用epoll_wait獲得就緒文件描述符時,返回的不是實際的描述符,而是一個代表描述符數量的值,我們只需要在epoll指定的一個數組中依次取得相應數量的文件描述符即可。這裏也使用了內存映射技術,這裏也省掉了這些描述符在系統調用時複製的開銷。

        epoll採用基於事件的就緒通知方式。在select/poll中,進程只有在調用一定的方法後,內核纔對所有監視的文件描述符進行掃描,而epoll事先通過epoll_ctl的回掉機制,迅速激活這個文件描述符,當進程調用epoll_wait時便得到通知。

三、水平觸發(LT)和邊緣觸發 (ET)

(1)Level Triggered工作模式

    以LT調用epoll接口的時候,就相當於一個速度比較快的poll(2)。LT是epoll的缺省工作方式,同時支持阻塞和非阻塞。在這種做法中,內核告訴你一個文件描述符是否就緒了,然後再對這個就緒的fd進行IO操作,當不做任何操作的時候,內核還是會繼續通知你的。所以,這種模式編程出錯的可能性要小一些,傳統的select/poll就是這種模型的代表。

代碼描述:

  1 #include<stdio.h>                                                           
  2 #include<stdlib.h>
  3 #include<sys/epoll.h>
  4 #include<sys/types.h>
  5 #include<sys/socket.h>
  6 #include<netinet/in.h>
  7 #include<arpa/inet.h>
  8 #include<unistd.h>
  9 #include<fcntl.h>
 10 #include<assert.h>
 11 #include<errno.h>
 12 #include<string.h>
 13 
 14 #define _BACKLOG_ 5
 15 #define _SIZE_ 64
 16 #define _MAX_FD_SIZE_ 64
 17 #define _BUF_SIZE_ 10240
 18 
 19 typedef struct data_buf
 20 {
 21     int fd;
 22     char buf[_BUF_SIZE_];
 23 }data_buf_t,*data_buf_p;
 24 
 25 static void Usage(char* const proc)
 26 {
 27     assert(proc);
 28     printf("%s [ip][port]\n",proc);
 29 }
 30 
 31 static int startup(char *ip,int port)
 32 {
 33     assert(ip);
 34     int sock=socket(AF_INET,SOCK_STREAM,0);
 35     if(sock<0)
 36     {
 37         perror("socket");
 38         exit(1);
 39     }
 40     
 41     int opt=1;
 42     setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
 43 
 44     struct sockaddr_in local;
 45     local.sin_family=AF_INET;
 46     local.sin_port=htons(port);
 47     local.sin_addr.s_addr=inet_addr(ip);
 48 
 49     if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
 50     {
 51         perror("bind");
 52         exit(2);
 53     }
 54     if(listen(sock,_BACKLOG_)<0)
 55     {
 56         perror("listen");
 57         exit(3);
 58     }
 59     return sock;
 60 }
 61 
 62 //sock=listen_sock
 63 static int server_epoll(int sock)
 64 {
 65     int epoll_fd=epoll_create(_SIZE_);
 66     if(epoll_fd<0)
 67     {     
 68         perror("epoll_create");
 69         return -1;
 70     }
 71 
 72     struct epoll_event ev;
 73     ev.data.fd=sock;
 74     ev.events=EPOLLIN;
 75     if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,sock,&ev)<0)
 76     {
 77         perror("epoll_ctl");
 78         return -2;
 79     }   
 80     struct epoll_event ev_out[_MAX_FD_SIZE_];
 81     int max=_MAX_FD_SIZE_;
 82 
 83     int timeout=5000;
 84     int i=0;
 85     int num=-1;
 86     while(1)
 87     {
 88         switch(num=epoll_wait(epoll_fd,ev_out,max,timeout))  
 89         {
 90             case -1://errno
 91                 perror("epoll_wait");
 92                 break;
 93             case 0://timeout
 94                 printf("timeout...\n");
 95                 break;
 96             default://data ready
 97                 {
 98                     for(i=0;i<num;++i)
 99                     {
100                         //get a new connect
101                         if(ev_out[i].data.fd==sock&&ev_out[i].events & EPOLL    IN)
102                         {
103                             struct sockaddr_in client;
104                             socklen_t len=sizeof(client);
105 
106                             int fd=ev_out[i].data.fd;
107                             int new_sock=accept(fd,(struct sockaddr*)&client    ,&len);
108                             if(new_sock<0)
109                             {                                                        110                                 perror("accept");
111                                 printf("%s:%d\n",strerror(errno),new_sock);
112                                 continue;
113                             }
114                             ev.events=EPOLLIN;
115                             ev.data.fd=new_sock;
116                             epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&ev);
117                             printf("get a new connect...\n");
118                         }
119                         else if(ev_out[i].data.fd>0&&ev_out[i].events & EPOL    LIN)
120                         {
121                             int fd=ev_out[i].data.fd;
122                             data_buf_p mem=(data_buf_p)malloc(sizeof(data_bu    f_t));
123                             if(mem==NULL)
124                             {
125                                 continue;   
126                             }
127                             mem->fd=fd;                                     
128                             int _s=read(mem->fd,mem->buf,sizeof(mem->buf));          129                             if(_s<0)
130                             {
131                                 perror("read");
132                                 close(fd);
133                                 free(mem);
134                             }
135                             else if(_s==0)
136                             {
137                                 printf("client close...\n");
138                                 epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);
139                                 close(fd);
140                                 free(mem);
141                             }
142                             else if(_s>0)
143                             {
144                                 mem->buf[_s]='\0';
145                                 printf("client say:%s\n",mem->buf);
146                                 ev.events=EPOLLOUT;
147                                 ev.data.ptr=mem;
148                                 epoll_ctl(epoll_fd,EPOLL_CTL_MOD,mem->fd,&ev    );   149                             }
150                             else
151                             {
152                                 continue;
153                             }
154                         }
155             
156                         else if(ev_out[i].data.fd>0&&ev_out[i].events & EPOL    LOUT)
157                         {
158                             data_buf_p mem=(data_buf_p)ev_out[i].data.ptr;
159                             ssize_t _s=write(mem->fd,mem->buf,sizeof(mem->bu    f));
160                             close(mem->fd);
161                             epoll_ctl(epoll_fd,EPOLL_CTL_DEL,mem->fd,NULL);
162                         }                                                            
173                             else
174                             {}       
175                     }
176                 break;
177                 }
178         }
179     }
180 }
181 
182 int main(int argc,char *argv[])
183 {
184     if(argc!=3)
185     {
186         Usage(argv[0]);
187         exit(1);
188     }
189     char *ip=argv[1];
190     int port=atoi(argv[2]);
191     int listen_sock=startup(ip,port);
192     server_epoll(listen_sock);
193     close(listen_sock);
194     return 0;
195 }

運行結果:

    用telnet測試:

wKioL1ePXbmhmIReAAA6wAdho7I688.png

wKioL1ePXcrg40SGAAA-MKXZa9I816.png

    用瀏覽器測試,則是:

wKiom1ePXuLSpULHAADPNbW46GI956.png

    若修改代碼爲:

164                             else if(ev_out[i].data.fd>0&&ev_out[i].events &     EPOLLOUT)
165                             {
166                                 
167                                 char *msg="HTTP/1.0 200 OK\r\n\r\nhello worl    d\r\n";
168                                 data_buf_p mem=(data_buf_p)ev_out[i].data.pt    r;
169                                 ssize_t _s=write(mem->fd,msg,strlen(msg));
170                                 close(mem->fd);
171                                 epoll_ctl(epoll_fd,EPOLL_CTL_DEL,mem->fd,NUL    L);
172                             }

    則結果爲:   wKioL1ePYI-ilgXjAAEBW0KMS9M964.png(2)Edge Triggered工作模式

    如果將文件句柄添加到epoll描述符的時候使用了EPOLLET標誌,那麼在調用epoll_wait(2)之後就有可能會被掛起,因爲剩餘的數據還存在於文件的輸入緩衝區中,而且數據發出端還在等待一個針對已經發出的數據的反饋信息。只有在監視的文件句柄上發生了某個事件的時候ET工作模式纔會彙報事件。因此在epoll_wait(2)的時候,調用者可能會放棄等待仍存在於文件輸入緩衝區內的剩餘數據。因此最好以下面的方式調用ET模式:

    a、基於非阻塞文件句柄

    b、只有當read(2)或write(2)返回EAGAIN時才需要掛起,等待。但這並不是說每次read()時都需要循環讀,直到讀到一個EAGAIN時才認爲此次時間處理完成。當read()返回的讀到的數據長度小於請求的數據長度時,就可以確定此時緩衝區中沒有數據了。也就可以認爲此事件已處理完成。

代碼描述:

  1 #include<stdio.h>                                                           
  2 #include<stdlib.h>
  3 #include<sys/epoll.h>
  4 #include<sys/types.h>
  5 #include<sys/socket.h>
  6 #include<netinet/in.h>
  7 #include<arpa/inet.h>
  8 #include<unistd.h>
  9 #include<fcntl.h>
 10 #include<assert.h>
 11 #include<errno.h>
 12 #include<string.h>
 13 
 14 #define _BACKLOG_ 5
 15 #define _SIZE_ 256
 16 #define _MAX_FD_SIZE_ 64
 17 #define _BUF_SIZE_ 10240
 18 
 19 typedef struct data_buf
 20 {
 21     int fd;
 22     char buf[_BUF_SIZE_];
 23 }data_buf_t,*data_buf_p;
 24 
 25 static int set_non_block(int fd)
 26 {
 27     int old_fl=fcntl(fd,F_GETFL);
 28     if(old_fl<0)
 29     {
 30         perror("fcntl");
 31         return -1;
 32     }
 33     if(fcntl(fd,F_SETFL,old_fl|O_NONBLOCK))
 34     {
 35         perror("fcntl");
 36         return -2;
 37     }
 38     return 0;
 39 }
 40 
 41 static void Usage(char* const proc)
 42 {
 43     assert(proc);
 44     printf("%s [ip][port]\n",proc);
 45 }
 46     
 47 static int startup(char *ip,int port)
 48 {
 49     assert(ip);
 50     int sock=socket(AF_INET,SOCK_STREAM,0);
 51     if(sock<0)
 52     {
 53         perror("socket");
 54         exit(1);
 55     }
 56     
 57     int opt=1;
 58     setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
 59 
 60     struct sockaddr_in local;
 61     local.sin_family=AF_INET;
 62     local.sin_port=htons(port);
 63     local.sin_addr.s_addr=inet_addr(ip);
 64 
 65     if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
 66     {
 67         perror("bind");
 68         exit(2);   
 69     }
 70     if(listen(sock,_BACKLOG_)<0)
 71     {
 72         perror("listen");
 73         exit(3);
 74     }
 75     return sock;
 76 }
 77 
 78 
 79 int read_data(int fd,char *buf,int size)
 80 {
 81     assert(buf);
 82     memset(buf,'\0',size);
 83     int index=0;
 84     ssize_t _s=-1;
 85     while(_s=read(fd,buf+index,size-index)<size)
 86     {
 87         if(errno==EAGAIN)
 88         {
 89             break;
 90         }
 91         index+=_s;     
 92     }
 93     return index;
 94 }
 95 
 96 int write_data(int fd,char *buf,int size)
 97 {
 98     int index=0;
 99     ssize_t _s=-1;
100     while(_s=write(fd,buf+index,size-index)<size)
101     {
102         if(errno==EAGAIN)
103         {
104             break;
105         }
106         index+=_s;
107     }
108     return index;
109 }
110 
111 //sock=listen_sock
112 static int server_epoll(int sock)
113 {   
114     int epoll_fd=epoll_create(_SIZE_);
115     if(epoll_fd<0)
116     {
117         perror("epoll_create");
118         return -1;
119     }
120 
121     struct epoll_event ev;
122     ev.data.fd=sock;
123     ev.events=EPOLLIN|EPOLLET;
124     if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,sock,&ev)<0)
125     {
126         perror("epoll_ctl");
127         return -2;
128     }   
129     struct epoll_event ev_out[_MAX_FD_SIZE_];
130     int max=_MAX_FD_SIZE_;
131 
132     int timeout=5000;
133     int i=0;
134     int num=-1;
135     int done=0;
136     while(!done)    
137     {
138         switch(num=epoll_wait(epoll_fd,ev_out,max,timeout))
139         {
140             case -1://errno
141                 perror("epoll_wait");
142                 break;
143             case 0://timeout
144                 printf("timeout...\n");
145                 break;
146             default://data ready
147                 {
148                     for(i=0;i<num;++i)
149                     {
150                         //get a new connect
151                         if(ev_out[i].data.fd==sock&&(ev_out[i].events&EPOLLI    N))
152                         {
153                             struct sockaddr_in client;
154                             socklen_t len=sizeof(client);
155 
156                             int fd=ev_out[i].data.fd;
157                             int new_sock=accept(fd,(struct sockaddr*)&client    ,&len);                                                                                       158                             if(new_sock<0)
159                             {
160                                 perror("accept");
161                                 printf("%s:%d\n",strerror(errno),new_sock);
162                                 continue;
163                             }
164                             set_non_block(new_sock);
165                             ev.events=EPOLLIN|EPOLLET;
166                             ev.data.fd=new_sock;
167                             epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&ev);
168                             printf("get a new connect...\n");
169                         }
170                         else
171                         {
172                             //read events ready
173                             if(ev_out[i].events&EPOLLIN)
174                             {
175                                 int fd=ev_out[i].data.fd;
176                                 data_buf_p mem=(data_buf_p)malloc(sizeof(dat    a_buf_t));
177                                 if(mem==NULL)
178                                 {                                           
179                                     perror("malloc");    
180                                     continue;
181                                 }
182                                 //read data all done...
183                                 mem->fd=fd;
184                                 ssize_t _s=read_data(mem->fd,mem->buf,sizeof    (mem->buf));
185                                 if(_s>0)
186                                 {
187                                     (mem->buf)[_s]='\0';
188                                     ev.data.ptr=mem;
189                                     printf("client:%s\n",mem->buf);
190                                     ev.events=EPOLLOUT|EPOLLET;
191                                     ev.data.ptr=mem;
192                                     epoll_ctl(epoll_fd,EPOLL_CTL_MOD,fd,&ev)    ;
193                                 }
194                                 else if(_s==0)
195                                 {
196                                     printf("client close...\n");
197                                     epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL    );
198                                     close(fd);
199                                     free(mem);     
200                                 }
201                                 else
202                                 {
203                                     continue;
204                                 }
205                             }
206                             else if(ev_out[i].events&EPOLLOUT)
207                             {
208                                 data_buf_p mem=(data_buf_p)ev_out[i].data.pt    r;
209                                 write_data(mem->fd,mem->buf,strlen(mem->buf)    );
210                                 close(mem->fd);
211                                 epoll_ctl(epoll_fd,EPOLL_CTL_DEL,mem->fd,NUL    L);
212                                 free(mem);
213                             }
214                             else
215                             {
216                             }
217                         }
218                     }
219                 }
220                 break;
221         }   
222     }
223 }
224 
225 int main(int argc,char *argv[])
226 {
227     if(argc!=3)
228     {
229         Usage(argv[0]);
230         exit(1);
231     }
232     char *ip=argv[1];
233     int port=atoi(argv[2]);
234     int listen_sock=startup(ip,port);
235     server_epoll(listen_sock);
236     close(listen_sock);
237     return 0;
238 }
239

    運行結果:

wKioL1ePdsjjwOxKAAAr0BGmLv8071.png

wKiom1ePdt-g9CBPAAA927_-evs983.png

        epoll首先調用epoll_create建立一個epoll對象,參數size是內核保證能夠正確處理的最大句柄數,多於這個最大數時內核可不保證效果。

        epoll_ctl可以操作上面的epoll,例如,將剛建立的socket加入到epoll中讓其監控,或者把epoll正在監控的某個socket句柄移出epoll,不再監控它等等。

        epoll_wait在調用時,在給定的timeout時間內,當在監控的所有句柄中有事件發生時,就返回用戶態的進程。

        因此可以看出epoll優於select/poll:因爲後者每次調用時都要傳遞你所要監控的所有socket給socket/poll系統調用,這意味着需要將用戶態的socket列表copy到內核態,如果以萬計的句柄每次都要copy幾十幾百KB的內存到內核態,非常低效。而調用epoll_wait時就相當於以往調用select/poll,但是這時卻不用傳遞socket句柄給內核,因爲內核已經在epoll_ctl中拿到了要監控的句柄列表。

     

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章