1.首先介紹一下select和epoll的異同,以下(摘抄自https://www.cnblogs.com/Anker/p/3265058.html)html
select的幾大缺點:git
(1)每次調用select,都須要把fd集合從用戶態拷貝到內核態,這個開銷在fd不少時會很大github
(2)同時每次調用select都須要在內核遍歷傳遞進來的全部fd,這個開銷在fd不少時也很大服務器
(3)select支持的文件描述符數量過小了,默認是1024socket
epoll的解決方案:函數
既然是對select和poll的改進,就應該能避免上述的三個缺點。那epoll都是怎麼解決的呢?在此以前,咱們先看一下epoll和select和poll的調用接口上的不一樣,select和poll都只提供了一個函數——select或者poll函數。而epoll提供了三個函數,epoll_create,epoll_ctl和epoll_wait,epoll_create是建立一個epoll句柄;epoll_ctl是註冊要監聽的事件類型;epoll_wait則是等待事件的產生。性能
對於第一個缺點,epoll的解決方案在epoll_ctl函數中。每次註冊新的事件到epoll句柄中時(在epoll_ctl中指定EPOLL_CTL_ADD),會把全部的fd拷貝進內核,而不是在epoll_wait的時候重複拷貝。epoll保證了每一個fd在整個過程當中只會拷貝一次。spa
對於第二個缺點,epoll的解決方案不像select或poll同樣每次都把current輪流加入fd對應的設備等待隊列中,而只在epoll_ctl時把current掛一遍(這一遍必不可少)併爲每一個fd指定一個回調函數,當設備就緒,喚醒等待隊列上的等待者時,就會調用這個回調函數,而這個回調函數會把就緒的fd加入一個就緒鏈表)。epoll_wait的工做實際上就是在這個就緒鏈表中查看有沒有就緒的fd(利用schedule_timeout()實現睡一會,判斷一會的效果,和select實現中的第7步是相似的)。code
對於第三個缺點,epoll沒有這個限制,它所支持的FD上限是最大能夠打開文件的數目,這個數字通常遠大於2048,舉個例子,在1GB內存的機器上大約是10萬左右,具體數目能夠cat /proc/sys/fs/file-max察看,通常來講這個數目和系統內存關係很大。server
總結:
(1)select,poll實現須要本身不斷輪詢全部fd集合,直到設備就緒,期間可能要睡眠和喚醒屢次交替。而epoll其實也須要調用epoll_wait不斷輪詢就緒鏈表,期間也可能屢次睡眠和喚醒交替,可是它是設備就緒時,調用回調函數,把就緒fd放入就緒鏈表中,並喚醒在epoll_wait中進入睡眠的進程。雖然都要睡眠和交替,可是select和poll在「醒着」的時候要遍歷整個fd集合,而epoll在「醒着」的時候只要判斷一下就緒鏈表是否爲空就好了,這節省了大量的CPU時間。這就是回調機制帶來的性能提高。
(2)select,poll每次調用都要把fd集合從用戶態往內核態拷貝一次,而且要把current往設備等待隊列中掛一次,而epoll只要一次拷貝,並且把current往等待隊列上掛也只掛一次(在epoll_wait的開始,注意這裏的等待隊列並非設備等待隊列,只是一個epoll內部定義的等待隊列)。這也能節省很多的開銷。
上面是簡單介紹select和epoll的異同,下面咱們看看epoll如何實現一個聊天室功能
需求:咱們想要構建一個聊天室,而且聊天室能夠進行分組,對於不一樣組的用戶之間是不能進行聊天的。
實現:
1.server端
(1)初始化sockaddr_in變量
struct sockaddr_in serverAddr; serverAddr.sin_family=PF_INET; serverAddr.sin_port=htons(SERVER_PORT); serverAddr.sin_addr.s_addr=inet_addr(SERVER_IP);
(2)聲明一個sock變量,bind,listen等
int listener=socket(PF_INET,SOCK_STREAM,0); if(listener<0){ perror("socket error"); exit(-1); } printf("socket created\n"); if(bind(listener,(struct sockaddr *)& serverAddr,sizeof(serverAddr))<0){ perror("bind error"); exit(-1); } int ret=listen(listener,5); if(ret<0){ perror("listen error"); exit(-1); } printf("start to listen: %s\n",SERVER_IP);
(3)建立epoll註冊表
int epfd=epoll_create(EPOLL_SIZE); if(epfd<0){ perror("epoll_create error"); exit(-1); } printf("epoll created,epollfd=%d\n",epfd); static struct epoll_event events[EPOLL_SIZE]; addfd(epfd,listener,true);
其中addfd函數將listener加入到了註冊表中。
(4)開始處理對應的時間
while(1){ int epoll_events_count=epoll_wait(epfd,events,EPOLL_SIZE,-1);//epoll_events_count表示被觸發的事件總數。 if(epoll_events_count<0){ perror("epoll error"); break; } printf("epoll_events_count=%d\n",epoll_events_count); for(int i=0;i<epoll_events_count;i++){ int sockfd=events[i].data.fd; if(sockfd==listener){//表示有新的客戶端連接進來了 struct sockaddr_in client_address; socklen_t client_addrLength=sizeof(struct sockaddr_in); int clientfd=accept(listener,(struct sockaddr *)&client_address,&client_addrLength); printf("client connection from:%s : %d (IP:port),clientfd=%d\n",inet_ntoa(client_address.sin_addr),ntohs(client_address.sin_port),clientfd); //cout<<"which chat room: "<<endl; //cin>>MchatId; char message[BUF_SIZE]; bzero(message,BUF_SIZE); sprintf(message,"welcome %d\n which chat room? ",clientfd); int ret=send(clientfd,message,BUF_SIZE,0); //發送歡迎信息 if(ret<0){ perror("send error"); exit(-1); } //recv(clientfd,buf,BUF_SIZE,0); char Mnums[1]; ret=recv(clientfd,Mnums,sizeof(Mnums),0);//接受用戶選擇的聊天室組號 if(ret<0){ perror("choose error"); exit(-1); } int MchatId=Mnums[0]-'0'; cout<<"MchatId: "<<MchatId<<endl; MUsers * uu=new MUsers(clientfd); uu->chatId=MchatId; clients_list.push_back(*uu); printf("Add new clientfd=%d to epoll\n",clientfd); printf("Now there are %d clients in the chatroom\n",(int)clients_list.size()); printf("welcome message\n"); addfd(epfd,clientfd,true); }else{ int ret=sendBroadcastmessage(sockfd);//對同一組的用戶進行廣播 if(ret<0){ perror("sendBroad error"); exit(-1); } } } }
2.clients端
(1)初始化sockaddr_in變量
struct sockaddr_in serverAddr; serverAddr.sin_family=PF_INET; serverAddr.sin_port=htons(SERVER_PORT); serverAddr.sin_addr.s_addr=inet_addr(SERVER_IP);
(2)聲明socket,connect
int sock=socket(PF_INET,SOCK_STREAM,0); if(sock<0){ perror("sock error"); exit(-1); } if(connect(sock,(struct sockaddr *)&serverAddr,sizeof(serverAddr))<0){ perror("connect error"); exit(-1); }
(3)使用pipe管道,並初始化epoll註冊表,並將sock和讀管道加入到註冊表中。
int pipe_fd[2]; if(pipe(pipe_fd)<0){ perror("pipe error"); exit(-1); } int epfd=epoll_create(EPOLL_SIZE); if(epfd<0){ perror("epfd error"); exit(-1); } static struct epoll_event events[2]; addfd(epfd,sock,true); addfd(epfd,pipe_fd[0],true);//加入讀管道是爲了獲取子進程經過寫管道發送的信息 bool isClientwork=true; char message[BUF_SIZE];
(4)子進程負責將標準輸入中的數據發送到寫管道,父進程經過讀管道得到標準輸入中的數據,而後發送到客戶端處理。同時父進程還要負責接受客戶端發來的消息。
int pid=fork(); if(pid<0){ perror("fork error"); exit(-1); } else if(pid==0){ close(pipe_fd[0]); printf("Please input 'exit' to exit the chat room\n"); while(isClientwork){ bzero(&message,BUF_SIZE); fgets(message,BUF_SIZE,stdin);//獲取標準輸入的數據 if(strncasecmp(message,EXIT,strlen(EXIT))==0){ isClientwork=false; }else{ if(write(pipe_fd[1],message,strlen(message)-1)<0){//經過寫管道發送給父進程 perror("fork error"); exit(-1); } } } }else{ close(pipe_fd[1]); while(isClientwork){ int epoll_events_count=epoll_wait(epfd,events,2,-1); for(int i=0;i<epoll_events_count;++i){ bzero(&message,BUF_SIZE); if(events[i].data.fd==sock){//服務器來的消息 int ret=recv(sock,message,BUF_SIZE,0);//接受來自服務器的信息 if(ret==0){ printf("server closed connection: %d\n",sock); close(sock); isClientwork=false; }else{ printf("%s\n",message); } }else{ int ret=read(events[i].data.fd,message,BUF_SIZE);//得到標準輸入的信息 if(ret==0) isClientwork=0; else{ send(sock,message,BUF_SIZE,0);//發送到服務器 } } } } }
完整代碼:https://github.com/JsonZhangAA/shiyanlou/tree/master/epoll