溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

高級(jí)I/O---多路復(fù)用---epoll

發(fā)布時(shí)間:2020-03-30 19:06:32 來源:網(wǎng)絡(luò) 閱讀:519 作者:wpfbcr 欄目:網(wǎng)絡(luò)安全

多路復(fù)用之epoll


  作為多路復(fù)用中最高效的I/O,epoll有著select和poll都不具有的很多能力。

不同于poll和select,epoll它用三個(gè)函數(shù)來實(shí)現(xiàn)多路復(fù)用這一個(gè)功能。


    #include <sys/epoll.h>

       int epoll_create(int size);
       //用于創(chuàng)建一個(gè)epoll模式的存儲(chǔ)空間,返回值是一個(gè)文件描述符,后面和函數(shù)中
       //都會(huì)用到這個(gè)epoll_fd。
        
       int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
       //epoll_ctl用于添加一個(gè)事件到epfd中,op表示方式有EPOLL_ADD,EPOLL_DEL
       //EPOLL_MOD方式,fd表示你要添加進(jìn)去的文件描述符,后面是一個(gè)結(jié)構(gòu)體指針,
       //結(jié)構(gòu)體在下面會(huì)說到。
      
       int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
       //epoll_wait用于等待事件的發(fā)生,這個(gè)結(jié)構(gòu)體指針會(huì)儲(chǔ)存返回來的fd,maxevents
       //表示最大能夠接收到的fd的個(gè)數(shù),注意能接收到的fd的個(gè)數(shù)很多(查閱一些資料這個(gè)數(shù)據(jù)
       //在1G內(nèi)存的機(jī)子上大約能有10萬余個(gè))。
                  
           typedef union epoll_data {
               void        *ptr;
               int          fd;
               __uint32_t   u32;
               __uint64_t   u64;
           } epoll_data_t;

           struct epoll_event {
               __uint32_t   events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };
           //這個(gè)結(jié)構(gòu)體中包含一個(gè)聯(lián)合體和一個(gè)events,events是用來描述觸發(fā)狀態(tài),
           //可以設(shè)置為EPOLLIN,EPOLLOUT,EPOLLD等。
           //聯(lián)合體中我們關(guān)注fd和*ptr因?yàn)槁?lián)合體有時(shí)候會(huì)有bug產(chǎn)生,如果我們用str來
           //存放讀取的數(shù)據(jù)的時(shí)候,我們可以讓這個(gè)ptr指向一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體中設(shè)置
           //fd和*buf參數(shù)。


epoll之所以比之前的多路復(fù)用高效主要原因有以下幾個(gè)

1>:epoll的組織方式,它是以兩個(gè)一個(gè)很高效的結(jié)構(gòu)體紅黑樹,list鏈表

 紅黑樹用于存放fd,一旦有事件發(fā)生它能夠以O(shè)(1)的時(shí)間復(fù)雜度找到并且

 把它放到list中,這樣發(fā)回值就是這個(gè)event結(jié)構(gòu)體指針,它里面存放的

 便是發(fā)生事件的fd,從之前多路復(fù)用的輪訓(xùn)O(N),減少到O(1),可見它的

 高效之處。

2>:觸發(fā)方式:epoll可以使用兩種觸發(fā)方式來獲取事件;下面會(huì)說到的水平

 觸發(fā)和邊緣觸發(fā)。使用邊緣觸發(fā)方式可以使epoll更加高效。



邊緣觸發(fā)和水平觸發(fā)


水平觸發(fā)PT:epoll_wait一旦fd中發(fā)生狀態(tài)變化假設(shè)狀態(tài)變化是read,并且如果沒有讀完,下次會(huì)繼續(xù)提醒,直到把緩沖區(qū)fd中的數(shù)據(jù)讀完為止。

邊緣觸發(fā)ET:epoll_wait當(dāng)fd中狀態(tài)發(fā)生變化時(shí)假設(shè)狀態(tài)變化是read,它會(huì)在第一次提醒,如果沒有把它讀完則后面不會(huì)再提醒,除非有新的數(shù)據(jù)到來才會(huì)接著上次的往后面讀。

在epoll_wait下要將套接字用fcntl函數(shù)設(shè)置為非阻塞,為什么要設(shè)置未非阻塞呢?想了好久的我終于發(fā)現(xiàn),在一次ET中因?yàn)椴⒉槐WC把緩沖區(qū)中的數(shù)據(jù)徹底讀完,而阻塞模式下的sock是要保證把fd中的數(shù)據(jù)讀完的,兩者矛盾,會(huì)導(dǎo)致不接受新到來的fd。

ET需要用到的是自定義的read函數(shù),如下所示:

 57 int read_fd(int sock,char *buf,int size)
 58 {
 59     int _size=-1;
 60     int index=0;
 61     while((_size=read(sock,buf+index,size-index)))
 62     {
 63         if(_size<0&&errno==EAGAIN)
 64         {
 65             break;
 66         }
 67         index+=_size;
 68         _size=-1;
 69     }
 70     return index;
 71 }
 //因?yàn)镋T模式的特點(diǎn)必須保證一次把整個(gè)sock中的一次數(shù)據(jù)全部讀完,不然如果沒有下次的數(shù)據(jù)
 //到來,前面沒有讀完的數(shù)據(jù)就會(huì)永久性的丟失了。
 //當(dāng)read讀到sock中整個(gè)數(shù)據(jù)流的最末尾的時(shí)候會(huì)產(chǎn)生一個(gè)類似errno的信號(hào)EAGAIN告訴它已經(jīng)
 //讀到了sock中的最后一個(gè)數(shù)據(jù)。



下面是一個(gè)epollET模式下的client與server

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<sys/socket.h>
  4 #include<sys/types.h>
  5 #include<arpa/inet.h>
  6 #include<netinet/in.h>
  7 #include<sys/epoll.h>
  8 #include<fcntl.h>
  9 #include<errno.h>
 10 #include<string.h>
 11 #include<unistd.h>
 12 
 13 #define _MAX_LISTEN_ 6
 14 #define _MAX_EPFD_ 64
 15 #define _MAX_BUF_ 1024
 16 
 17 void nonblock(int sock)
 18 {
 19     int fl=fcntl(sock,F_GETFL);
 20     if(fl<0)
 21     {
 22         perror("fcntl");
 23         exit(2);
  24     }
 25     if(fcntl(sock,F_SETFL,fl|O_NONBLOCK)<0)
 26     {
 27         exit(3);
 28     }
 29 }
 30 int Listensock(char *ip,int port)
 31 {
 32     int sock=socket(AF_INET,SOCK_STREAM,0);
 33     if(sock<0)
 34     {
 35         perror("socket");
 36         exit(1);
 37     }
 38     nonblock(sock);
 39     struct sockaddr_in local;
 40     local.sin_addr.s_addr=inet_addr(ip);
 41     local.sin_port=htons(port);
 42 
 43     if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
 44     {
 45         perror("bind");
 46     }
 47 
 48     if(listen(sock,_MAX_LISTEN_)<0)
 49     {
 50         perror("listen");
 51     }   }
 52 
 53     return sock;
 54 
 55 }
 56 
 57 int read_fd(int sock,char *buf,int size)
 58 {
 59     int _size=-1;
 60     int index=0;
 61     while((_size=read(sock,buf+index,size-index)))
 62     {
 63         if(_size<0&&errno==EAGAIN)
 64         {
 65             break;
 66         }
 67         index+=_size;
 68         _size=-1;
 69     }
 70     return index;
 71 }
 72 void epollserver(int sock)
 73 {
 74     int epfd=epoll_create(256);
 75 
 76     if(epfd<0)
 77     {
 78         perror("epoll_create");
 79         exit(4);
 80     }
 81 
 82     struct epoll_event ev;
 83     ev.data.fd=sock;
 84     ev.events=EPOLLIN|EPOLLET;
 85 
 86     if(epoll_ctl(epfd,EPOLL_CTL_ADD,sock,&ev)<0)
 87     {
 88         perror("epoll_ctl");
 89         exit(5);
 90     }
 91     struct epoll_event epfds[_MAX_EPFD_];
 92 
 93     int fd=-1;
 94     int i;
 95     for(i=0;i<_MAX_EPFD_;i++)
 96     {
 97         epfds[i].data.fd=fd;
 98     }
 99     int timeout=5000;
 100 
 101     int fdlen=0;
 102     while(1)
 103     {
 104         switch(fdlen=epoll_wait(epfd,epfds,_MAX_EPFD_,timeout))
 105         {
 106             case -1:
 107                 {
 108                     perror("epoll_wait");
 109                     continue;
 110                 }
 111             case 0:
 112                 {
 113                     printf("timeout\n");
 114                 }
 115             default:
 116                 {
 117                     struct sockaddr_in client;
 118                     int client_len=sizeof(client);
 119                     int i=0;
 120                     for(i=0;i<fdlen;i++)
 121                     {
122                         int retfd=epfds[i].data.fd; 
123                         if(retfd==sock&&(epfds[i].events&EPOLLIN))
124                         {                         
125                             int recvfd=accept(sock,(struct sockaddr*)\
126                                     &client,&client_len);
127                             if(recvfd<0)
128                             {
129                                 continue;
130                             }
131                             else
132                             {
133                                 nonblock(recvfd);
134                                 ev.data.fd=recvfd;
135                                 ev.events=EPOLLIN|EPOLLET;
136                                 if(epoll_ctl(epfd,EPOLL_CTL_ADD,recvfd,&ev)<0)
137                                 {
138                                     perror("epoll_ctl");
139                                     continue;
140                                 }
141                                 printf("a client come..ip=%s\n",\
142                                         inet_ntoa(client.sin_addr));
143 
144                             }
145                         }
146                         else if (epfds[i].events&EPOLLIN)
147                         {                        {
148                             char buf[_MAX_BUF_];
149                             memset(buf,'\0',_MAX_BUF_);
150                             int ret=read_fd(retfd,buf,_MAX_BUF_);
151                             if(ret>0)
152                             {
153                                 buf[ret-1]='\0';
154                                 printf("client ::%s\n",buf);
155                                 fflush(stdout);
156                             }else if(ret==0){
157                                 printf("ip=%s client is leave...\n",\
158                                         inet_ntoa(client.sin_addr));
159                             }else{
160                                 //doing noting
161                             }
162                         }//else{此處可以改成回顯}
163                 }
164             }
165 
166     }
167 }
168 
169 int main(int argc,char *argv[])
170 {
171     if(argc!=3)     if(argc!=3)
172     {
173         printf("[%s][ip][port]\n",argv[0]);
174     }
175     char *ip=argv[1];
176     int port=atoi(argv[2]);
177     int sock=Listensock(ip,port);
178 
179     int opt=1;
180 
181     if(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))<0)
182     {
183         perror("setsockopt");
184     }
185 
186     epollserver(sock);
187 
188     return 0;
189 }


高級(jí)I/O---多路復(fù)用---epoll




改為回顯模式:

當(dāng)讀完client的數(shù)據(jù)后,可以將event改為EPOLLOUT然后將讀到的數(shù)據(jù)保存起來

當(dāng)下次寫事件發(fā)生時(shí),將保存的數(shù)據(jù)扔出去。

注意:因?yàn)榻Y(jié)構(gòu)體中的data是一個(gè)聯(lián)合體,當(dāng)我們存放完fd后再去存放ptr有可能

會(huì)有bug,這里的方法是讓ptr指向一個(gè)結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體中保存著fd和buf。

typedef struct p_buf{
      int fd;
      char outbuf[_MAX_BUF_];
}p_buf;
//自定義的緩沖區(qū)




164                        else if (epfds[i].events&EPOLLIN)
166                         {
167                             ptrbuf *p_buf=(ptrbuf *)malloc(sizeof(ptrbuf));
                                //將數(shù)據(jù)直接讀到自定義的緩沖區(qū)中
168                             memset(p_buf->outbuf,'\0',sizeof(p_buf->outbuf));
169                             p_buf->fd=retfd;
170                             int ret=read_fd(retfd,p_buf->outbuf,_MAX_BUF_);
171 
172                             if(ret>0)
173                             {
174                                 p_buf->outbuf[ret-1]='\0';
175                                 printf("client ::%s\n",p_buf->outbuf);
176                                 fflush(stdout);
177                                 ev.events=EPOLLOUT|EPOLLET;
178                                 ev.data.ptr=p_buf;
                                      //設(shè)置為EPOLLOUT模式
179                                 if(epoll_ctl(epfd,EPOLL_CTL_MOD,retfd,&ev)<0)
180                                 {
181                                     perror("epoll_ctl");
182                                     continue;
183                                 }
184                             }else if(ret==0){
185                                 if(epoll_ctl(epfd,EPOLL_CTL_DEL,retfd,NULL)<0)
186                                 {
187                                     perror("epoll_ctl");
188                                 }
189                                 printf("ip=%s client is leave...\n",\
190                                         inet_ntoa(client.sin_addr));
191                             }else{
192                                 //doing noting
193                             }
194                         }else{//當(dāng)寫條件滿足時(shí),回顯消息并且改回為EPOLLIN模式
195                             ptrbuf* outptr=(ptrbuf*)epfds[i].data.ptr;
196                             int outfd=outptr->fd;
197                             outptr->outbuf[strlen(outptr->outbuf)]='\n';
198                             out_write(outfd,outptr->outbuf,_MAX_BUF_);
199                             free(outptr);
200                             ev.events=EPOLLIN|EPOLLET;
201                             ev.data.fd=outfd;
202                             if(epoll_ctl(epfd,EPOLL_CTL_MOD,outfd,&ev)<0)
203                             {
204                                 perror("epoll_ctl");
205                             }
206                         }
207                 }
208             }
209         }
210     }
211 }
212 
                                                                    194,6-24      96%                                                                                                                                           183,8-32      81%


回顯模式:

高級(jí)I/O---多路復(fù)用---epoll



總結(jié):

epoll對(duì)比之前的select和poll都有不小的改進(jìn),不用遍歷整個(gè)buf,沒有大小限制,并且有不同的模式可以選擇,效率之高可想而知。






向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI