您好,登錄后才能下訂單哦!
多路復(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 }
改為回顯模式:
當(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%
回顯模式:
總結(jié):
epoll對(duì)比之前的select和poll都有不小的改進(jìn),不用遍歷整個(gè)buf,沒有大小限制,并且有不同的模式可以選擇,效率之高可想而知。
免責(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)容。