使用epoll實現聊天室功能,同時比較epoll和select的異同

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

相關文章
相關標籤/搜索