epoll:結合了select與poll的優勢,以及優化了它們的不足,來實現同時控制多個句柄,以此來實現多路複用。它也是使用文件系統的相關信息來實現的
數組
它所使用的三個系統調用函數
瀏覽器
1.epoll_create函數
服務器
建立一個句柄,size大小可不關心,該句柄會佔用一個文件描述符位置
網絡
2.epoll_ctl函數,它須要使用一個結構體告訴內核需監聽什麼事件socket
它爲一個事件註冊函數,先將要監聽的何種事件進行註冊,不一樣於select函數,它是在監聽的時候就要告訴是何種事件ide
op指要對某個描述符進行何種操做(添加,刪除,更改)
函數
epoll_event該結構體第一個參數爲要監聽的事件類型,第二個參數爲一個聯合體類型,可爲一個fd,也可爲一個指針優化
3.使用epoll_wait函數spa
使用該函數它只需檢查哪一個就緒隊列(鏈表)是否爲空,用它能夠得到已經就緒的描述符
指針
maxevents表示一次最多容許返回多少個就緒事件個數
events是已經分配好的結構體數組(須要用戶本身分配),不能爲空
避免讓客戶端等待2MSL時間
當客戶端要進行四次揮手時,發送FIN後,會進入一個TIME-WAIT狀態等待2MSL,此時它與服務器端的鏈接還未真正斷開,所以在該時間內該用戶的端口號等信息不能被其它用戶所使用。所以爲了不等待2MSL,要使用setsockopt函數來實現端口複用功能
epoll的兩種工做方式:
LT:它支持阻塞套接字和非阻塞套接字,它讀取緩衝區中的數據時能夠讀一部分,由於當一個事件對應的套接字的緩衝區中還有內容時,內核會每次都告知你某個描述符已經就緒,能夠進行IO操做
ET(相對高效):數據只有從網絡上到達時纔會通知你一次,若是沒有任何的狀態改變,若一次沒有將某事件對應的套接字緩衝區內容處理完,它會一直在讀或寫時組等等待,這樣會使後續的多個描述符一直等待。所以必須將套接字設置爲非阻塞的,這樣當讀或寫返回一個EAGAIN時才須要等待,即當讀到的數據小於請求數據長度時說明緩衝區中的數據已經讀完。
服務器端:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <sys/types.h> 5 #include <sys/socket.h> 6 #include <errno.h> 7 #include <arpa/inet.h> 8 #include <netinet/in.h> 9 #include <assert.h> 10 #include <unistd.h> 11 #include <sys/epoll.h> 12 #include<fcntl.h> 13 #define _MAX_ 64 14 #define _MAX_SIZE_ 1024 15 #define BACK_LOG 5 16 typedef struct epoll_buf 17 { 18 int fd; 19 char buf[_MAX_SIZE_]; 20 }epoll_t,*epoll_data_p; 21 static void Usage(const char* proc) 22 { 23 assert(proc); 24 printf("Usage:%s [ip] [port]\n",proc); 25 } 26 27 static void SetNonBlock(int fd) 28 { //set fd to nonblock 29 int fds=0; 30 if(fds=fcntl(fd,F_GETFL)<0) 31 { 32 perror("fcntl"); 33 exit(1); 34 } 35 if(fcntl(fd,F_SETFL,fds|O_NONBLOCK)<0) 36 { 37 perror("fcntl"); 38 exit(2); 39 } 40 41 } 42 static int startup(char *ip,int port) 43 { 44 assert(ip); 45 int listen_sock=socket(AF_INET,SOCK_STREAM,0); 46 if(listen_sock < 0) 47 { 48 perror("socket"); 49 exit(1); 50 } 51 struct sockaddr_in local; 52 local.sin_family=AF_INET; 53 local.sin_port=htons(port); 54 local.sin_addr.s_addr=inet_addr(ip); 55 socklen_t len=sizeof(local); 56 int opt=1; //讓客戶端避免2MSL時間,實現端口複用 57 setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); 58 if(bind(listen_sock,(struct sockaddr*)&local,len)<0) 59 { 60 perror("bind"); 61 exit(2); 62 } 63 if(listen(listen_sock,BACK_LOG)<0) 64 { 65 perror("listen"); 66 exit(3); 67 } 68 return listen_sock; 69 } 70 int read_data(int fd,char *buf,int size) 71 { 72 assert(buf); 73 int ret=0; 74 int index=0; 75 memset(buf,'\0',sizeof(buf)); 76 while((ret=read(fd,buf+index,size-index))<size) 77 { 78 if(errno==EAGAIN) //客戶端已無數據發送 79 { 80 break; 81 } 82 index+=ret; 83 } 84 return index; 85 86 } 87 int write_data(int fd,char* buf,int size) 88 { 89 assert(buf); 90 int ret=0; 91 int index=0; 92 while((ret=write(fd,buf+index,size-index))<size) 93 { 94 if(errno==EAGAIN) 95 break; 96 index+=ret; 97 } 98 return index; 99 } 100 static int epoll_server(int listen_sock) 101 { 102 int epoll_fd=epoll_create(256); 103 if(epoll_fd < 0) 104 { 105 perror("epoll_create"); 106 exit(1); 107 } 108 int ready_num=-1; 109 struct epoll_event ev; 110 ev.events=EPOLLIN|EPOLLET; 111 ev.data.fd=listen_sock; 112 SetNonBlock(listen_sock);//將套接字設置爲非阻塞,因使用ET工做模式 113 struct epoll_event ret_ev[_MAX_]; 114 int num=_MAX_; 115 int timeout=5000; 116 struct sockaddr_in client; 117 socklen_t size=sizeof(client); 118 int i=0; 119 if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&ev)<0) 120 { 121 perror("epoll_ctl"); 122 123 exit(1); 124 } 125 int done=0; 126 while(!done) 127 { 128 switch(ready_num=epoll_wait(epoll_fd,ret_ev,num,timeout)) 129 {//只需檢查就緒隊列(就緒鏈表)是否爲空 130 case 0: 131 printf("timeout...\n"); 132 break; 133 case -1: 134 perror("epoll_wait"); 135 break; 136 default: 137 { 138 for(i=0;i<ready_num;++i) 139 { 140 if(ret_ev[i].data.fd==listen_sock &&\ 141 (ret_ev[i].events &EPOLLIN)) 142 {//判斷是否處於監聽狀態且所關心的事件類型 143 int fd=ret_ev[i].data.fd; 144 int new_sock=accept(fd,(struct sockaddr*)&client ,&size); 145 if(new_sock < 0) 146 { 147 perror("accept"); 148 exit(1); 149 } 150 printf("get a new connect...\n"); 151 SetNonBlock(new_sock); 152 ev.data.fd=new_sock; 153 ev.events=EPOLLIN|EPOLLET; 154 if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&ev )<0) 155 {//將new_sock添加到epoll中 156 perror("epoll_ctl"); 157 exit(2); 158 } 159 }else if(ret_ev[i].events&EPOLLIN) 160 { 161 int fd=ret_ev[i].data.fd; 162 163 epoll_data_p msg ; 164 msg=(epoll_data_p)malloc(sizeof(epoll_t)); 165 if(!msg) 166 { 167 perror("malloc"); 168 exit(1); 169 } 170 msg->fd=fd; 171 // ssize_t _s=read(msg->fd,msg->buf,\ 172 sizeof((msg->buf))-1); 173 int _s=read_data(msg->fd,msg->buf,\ 174 sizeof(msg->buf)-1); 175 if(_s > 0) 176 { 177 msg->buf[_s]='\0'; 178 printf("client:%s",msg->buf); 179 ev.data.ptr=msg; 180 ev.events=EPOLLOUT|EPOLLET; 181 if(epoll_ctl(epoll_fd,EPOLL_CTL_MOD,msg->fd, &ev)<0) 182 { 183 perror("epoll_ctl"); 184 return -1; 185 } 186 }else if(_s==0) 187 { 188 printf("client is closed...\n"); 189 free(msg); 190 close(msg->fd); 191 epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL); 192 193 } 194 }else if(ret_ev[i].events& EPOLLOUT) 195 { //write ready 196 epoll_data_p msg=(epoll_data_p)ret_ev[i].data.p tr; 197 // write(msg->fd,msg->buf,strlen(msg->buf)); 198 write_data(msg->fd,msg->buf,sizeof(msg->buf)); 199 // close(msg->fd); 200 // char* mem="HTTP/1.0 200 OK\r\n\r\n hello worl d\r\n"; 201 202 //write(msg->fd,mem,strlen(mem)); 203 ev.data.fd=msg->fd; 204 ev.events=EPOLLIN|EPOLLET; 205 epoll_ctl(epoll_fd,EPOLL_CTL_MOD,msg->fd,&ev); 206 //close(msg->fd); 207 // epoll_ctl(epoll_fd,EPOLL_CTL_DEL,msg->fd,NULL ); 208 //free(msg); 209 } 210 211 } 212 213 } 214 break; 215 } 216 } 217 return epoll_fd; 218 219 } 220 int main(int argc,char* argv[]) 221 { 222 if(argc!=3) 223 { 224 Usage(argv[0]); 225 exit(1); 226 } 227 char *_ip=argv[1]; 228 int _port=atoi(argv[2]); 229 int listen_sock=startup(_ip,_port); 230 epoll_server(listen_sock); 231 return 0; 232 }
客戶端代碼
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <assert.h> 5 #include <sys/types.h> 6 #include <errno.h> 7 #include <arpa/inet.h> 8 #include <netinet/in.h> 9 #include <sys/socket.h> 10 11 static void Usage(const char* proc) 12 { 13 assert(proc); 14 printf("Usage:%s [ip] [port]\n",proc); 15 } 16 17 int main(int argc,char* argv[]) 18 { 19 if(argc!=3) 20 { 21 Usage(argv[0]); 22 exit(1); 23 } 24 int sock=socket(AF_INET,SOCK_STREAM,0); 25 if(sock < 0) 26 { 27 perror("socket"); 28 exit(2); 29 } 30 char *_ip=argv[1]; 31 int _port=atoi(argv[2]); 32 struct sockaddr_in remote; 33 remote.sin_family=AF_INET; 34 remote.sin_port=htons(_port); 35 remote.sin_addr.s_addr=inet_addr(_ip); 36 socklen_t len=sizeof(remote); 37 if(connect(sock,(struct sockaddr*)&remote,len)<0) 38 { 39 perror("connect"); 40 return -1; 41 } 42 43 char buf[1024]; 44 while(1) 45 { 46 memset(buf,'\0',sizeof(buf)); 47 printf("please input:"); 48 fflush(stdout); 49 ssize_t _size=read(0,buf,sizeof(buf)-1); 50 if(_size >0) 51 { 52 buf[_size]='\0'; 53 write(sock,buf,strlen(buf)); 54 } 55 56 _size=read(sock,buf,sizeof(buf)-1); 57 if(_size>0) 58 { 59 printf("server---client:%s\n",buf); 60 } 61 } 62 close(sock); 63 return 0; 64 }
運行結果:
使用瀏覽器爲客戶端: