一、對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結構如下:
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測試:
用瀏覽器測試,則是:
若修改代碼爲:
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 }
如果將文件句柄添加到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
運行結果:
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中拿到了要監控的句柄列表。