高並發多路IO之select,poll和epoll模型區別與代碼實現


多路IO之select

優點:單進程下支持高並發,可以跨平台

缺點:多次從內核到應用,應用到內核的數組拷貝;

   每次內核都會重置填寫的數據

   最大支持1024客戶端,原因在於fd_set定義使用了FD_SETSIZE,大小為1024;

以下是select模型server代碼:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/select.h>
#include <ctype.h>

int main(){

    int lfd = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in serv;
    bzero(&serv,sizeof(serv));
    serv.sin_port = htons(8888);
    serv.sin_family = AF_INET;
    serv.sin_addr.s_addr = htonl(INADDR_ANY);

    //reset port
    int opt = 1;
    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    bind(lfd,(struct sockaddr*)&serv,sizeof(serv));

    listen(lfd,128);

    fd_set rdset;//readevent
    fd_set allset;//bak readevent
    FD_ZERO(&rdset);
    FD_SET(lfd,&rdset);
    allset = rdset;

    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    int nfds = lfd;
    int nready = 0;
    while(1){
        rdset = allset;//備份傳給內核
        //阻塞等待事件就緒
        nready = select(nfds+1,&rdset,NULL,NULL,NULL);
        if(FD_ISSET(lfd,&rdset)){
            //有新連接事件,將得到的CFD加入到集合
            int cfd = accept(lfd,(struct sockaddr*)&client,&len);
            if(cfd > 0){
                //rdset是一個傳入傳出集合,每次都會重置
                FD_SET(cfd,&allset);
            }
            if(nfds < cfd){
                nfds = cfd;
            }
            nready --;
            //如果就緒事件就一個,且是新連接就跳出循環
            if(nready <= 0)
                continue;
        }
        int i = 0;
        for(i = lfd+1;i<nfds +1;i++){
            if(FD_ISSET(i,&rdset)){
                char buf[256] = {0};
                int ret = read(i,buf,sizeof(buf));
                if(ret < 0){
                    perror("read err");
                    close(i);
                    FD_CLR(i,&allset);
                }
                else if (ret == 0){
                    close(i);//client closed
                    FD_CLR(i,&allset);
                }
                else{
                    int j = 0;
                    for(;j<ret;j++){
                        buf[j] = toupper(buf[j]);
                    }
                    write(i,buf,ret);
                }
                if(--nready <= 0)
                    break;//no event.jump for.
            }
        }
    }
    close(lfd);

    return 0;
}

 多路IO之POLL模型:

POLL的原理與select相同,比select改進的地方:

  1,請求和返回分離,避免每次都要重設數組

  2,可以突破1024限制,poll是由打開文件的上限決定,可以使用ulimit命令查看上限

  3,不能跨平台

poll代碼解析:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <poll.h>
#include <arpa/inet.h>

#define _MAXLINE_ 80
#define _SERVER_PORT_ 8888
#define _MAX_OPEN 1024

int main(){

    int i,maxi;
    char strIP[16];
    int lfd = socket(AF_INET,SOCK_STREAM,0);

    struct pollfd client[_MAX_OPEN];
    struct sockaddr_in clientaddr,servaddr;
    int len = sizeof(clientaddr);
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(_SERVER_PORT_);
    
    //set reuse port
    int opt = 1;
    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    if(bind(lfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0){
        perror("bind err");
        return -1;
    }
    listen(lfd ,128);

    client[0].fd = lfd;//監聽第一個文件描述符
    client[0].events = POLLIN;//監聽讀事件

    for(i = 1; i <_MAX_OPEN;i++){
        client[i].fd = -1;//用-1初始化,因為0也是描述符
    }

    maxi = 0;//記錄client數組有效最大元素下標
    
    while(1){
        int nready = poll(client,maxi+1,-1);
        //判斷是否有新連接
        if(client[0].revents & POLLIN){
            //此處不會阻塞
            int cfd = accept(lfd,(struct sockaddr*)&clientaddr,&len);
            printf("recv form %s:%d\n",
                   inet_ntop(AF_INET,&clientaddr.sin_addr,strIP,sizeof(strIP)),
                   ntohs(clientaddr.sin_port));
            for(i = 1;i<_MAX_OPEN;i++){
                if(client[i].fd < 0){
                    client[i].fd = cfd;
                    break;
                }
            }
            if(i == _MAX_OPEN){
                //最大客戶連接上限
                printf("max connected...\n");
                continue;
            }
            client[i].events = POLLIN;
            if (i > maxi)
                maxi = i;
            if(--nready <= 0)
                continue;//沒有更多就緒事件,繼續回到POLL阻塞

        }

        for(i = 1;i<=maxi;i++){
            //前面的IF沒有滿足,說明沒有新連接,而是讀事件
            int cfd;
            //先找到第一個大於0的文件描述符
            if((cfd = client[i].fd) < 0)
                continue;
            if(client[i].revents & POLLIN){
                char buf[_MAXLINE_] = {0};
                int ret = read(cfd,buf,sizeof(buf));
                if(ret < 0){
                    if(errno == ECONNRESET){
                        printf("client[%d] aborted connection\n",i);
                        close(cfd);
                        client[i].fd =-1;
                        //POLL中不需要像SELECT一樣移除,直接置-1即可
                    }
                    else{
                        perror("read error");
                        exit(-1);
                    }
                }
                else if(ret == 0){
                    printf("client[%d] closed\n",i);
                    close(cfd);
                    client[i].fd = -1;
                }
                else{
                    write(cfd,buf,ret);
                }
                if(--nready <= 0)
                    break;
            }
        }
    }
    close(lfd);
    return 0;
}

多路IO之EPOLL:

不管是select,還是poll,都需要遍歷數組輪詢,而且select僅支持1024個客戶端,在大量並發,少量活躍的情況下效率較低,也就滋生了epoll模型。

  1,可以突破1024限制,不跨平台

  2,無須遍歷整個文件描述符集,只需遍歷被內核IO事件異步喚醒,而加入ready隊列的文件描述符。

  3,除了select/poll的IO事件水平觸發(level triggered)外,還提供邊沿觸發(edge Triggered),可以緩存IO狀態,減少epoll_wait調用,提高效率

代碼原型如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/epoll.h>

int main(){

    int lfd = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in serv;
    bzero(&serv,sizeof(serv));
    serv.sin_addr.s_addr = htonl(INADDR_ANY);
    serv.sin_port = htons(8888);
    serv.sin_family = AF_INET;

    int opt = 1;
    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

    bind(lfd,(struct sockaddr*)&serv,sizeof(serv));
    
    listen(lfd,128);

    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    //create epoll node
    int epfd = epoll_create(1);
    struct epoll_event ev,evs[1024];
    ev.data.fd = lfd;
    ev.events = EPOLLIN;
    epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);

    while(1){
        int nready = epoll_wait(epfd,evs,1024,-1);
        if(nready > 0){
            int i = 0;
            for(;i<nready;i++){
                if(evs[i].data.fd == lfd){
                    if(evs[i].events & EPOLLIN){
                        int cfd = accept(lfd,(struct sockaddr*)&client,&len);
                        if(cfd > 0){
                            ev.data.fd = cfd;
                            //將新的連接上樹
                            epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
                        }
                    }
                }
                else{
                    //處理讀事
                    if(evs[i].events & EPOLLIN){
                        char buf[256]={0};
                        int ret = read(evs[i].data.fd,buf,sizeof(buf));
                        if(ret > 0){
                            write(evs[i].data.fd,buf,ret);
                        }
                        else if(ret == 0){
                            //client closed
                            close(evs[i].data.fd);
                            ev.data.fd = evs[i].data.fd;
                            epoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&ev);
                        }
                        else{
                            perror("read err");
                            close(evs[i].data.fd);
                            ev.data.fd = evs[i].data.fd;
                            epoll_ctl(epfd,EPOLL_CTL_DEL,evs[i].data.fd,&ev);
                        }
                    }
                }
            }
        }
    }
    close(epfd);
    close(lfd);

    return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM