高并发多路IO之select,poll和epoll模型区别与代码实现

≡放荡痞女 提交于 2020-01-28 07:53:34

多路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;
}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!