4.25 I/O多路复用技术

本文详细介绍了Linux下的I/O多路复用技术,包括select、poll和epoll。通过示例代码展示了如何使用这三种方法来监听和处理多个文件描述符,尤其是分析了select的限制和poll、epoll的改进。epoll利用红黑树和双链表优化了性能,并提供了边沿触发模式,提升了效率。

目录

简介

Select

POII

epoll


简介

I/O 多路复用使得程序能同时监听多个文件描述符,能够提高程序的性能,Linux 下实现 I/O 多路复用的 系统调用主要有 select、poll 和 epoll。

1.阻塞等待:

2.BIO模型

3.非阻塞忙轮询

NIO模型:

4.I/O多路复用技术

4.1select/poll

4.2expoll

Select

fd_set只有1024位 

select代码编写

#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/select.h>

int main()
{
    
    //创建socket
    int lfd = socket(PF_INET,SOCK_STREAM,0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    //绑定
    bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));

    //监听
    listen(lfd,8);

    //创建一个fd_set的集合,存放的是需要检测的文件描述符
    fd_set rdset,tmp;
    FD_ZERO(&rdset);
    FD_SET(lfd,&rdset);
    int maxfd = lfd;

    while(1){
        tmp=rdset;

        //调用select系统函数,让内核帮检测哪些文件描述符有数据
        int ret = select(maxfd+1,&tmp,NULL,NULL,NULL);
        if(ret == -1){
            perror("select");
            exit(-1);
        }else if(ret == 0){
            continue;
        }else if(ret > 0){
            //说明检测到了有文件描述符的对应的缓冲区的数据发生了改变
            if(FD_ISSET(lfd,&tmp)){
                //表示有新的客户端连接进来了
                struct sockaddr_in cliaddr;
                int len = sizeof(cliaddr);
                int cfd = accept(lfd,(struct sockaddr*)&cliaddr,&len);

                //将新的文件描述符加入到集合中
                FD_SET(cfd,&rdset);

                //更新最大的文件描述符
                maxfd = maxfd > cfd ? maxfd : cfd;
            }

            for(int i=lfd+1;i<=maxfd;i++){
                if(FD_ISSET(i,&tmp)){
                    //说明这个文件描述符对应的客户端发来了数据
                    char buf[1024]={0};
                    int len = read(i,buf,sizeof(buf));
                    if(len==-1){
                        perror("read");
                        exit(-1);
                    }else if(len==0){
                        printf("client closed...\n");
                        close(i);
                        FD_CLR(i,&rdset);
                    }else if(len>0){
                        printf("read buf = %s\n",buf);
                        write(i,buf,strlen(buf)+1);
                    }
                }
            }
        }
    }
    close(lfd);
    return 0;
}

对应client

#include<stdio.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

int main(){

    //创建socket
    int fd = socket(PF_INET,SOCK_STREAM,0);
    if(fd==-1){
        perror("socket");
        return -1;
    }

    struct sockaddr_in seraddr;
    inet_pton(AF_INET,"127.0.0.1",&seraddr.sin_addr.s_addr);
    seraddr.sin_family=AF_INET;
    seraddr.sin_port=htons(9999);

    //连接服务器
    int ret = connect(fd,(struct sockaddr*)&seraddr,sizeof(seraddr));

    if(ret == -1){
        perror("connect");
        return -1;
    }

    int num=0;
    while(1){
        char sendBuf[1024]={0};
        sprintf(sendBuf,"send data %d",num++);
        write(fd,sendBuf,strlen(sendBuf)+1);

        //接收
        int len = read(fd,sendBuf,sizeof(sendBuf));
        if(len == -1){
            perror("read");
            return -1;
        }else if(len>0){
            printf("read buf = %s\n",sendBuf);
        }else{
            printf("服务器已经断开连接...\n");
            break;
        }
        sleep(1);
    }
    close(fd);
    return 0;
}

POII

对select缺点进行一个改进(没有解决内核态和用户态转变的问题,只是解决了1024比特位的限制以及不能重用每次都需要重置的问题)同时也可以复用了

#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<poll.h>

int main(){
    
    //创建socket
    int lfd = socket(PF_INET,SOCK_STREAM,0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    //绑定
    bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));

    //监听
    listen(lfd,8);

    //初始化检测的文件描述符数组
    struct pollfd fds[1024];
    for(int i=0;i<1024;i++){
        fds[i].fd = -1;
        fds[i].events = POLLIN;
    }
    fds[0].fd=lfd;
    int nfds=0;

    while(1){
        //调用poll系统函数,让内核帮检测哪些文件描述符有数据
        int ret = poll(fds,nfds+1,-1);
        if(ret == -1){
            perror("poll");
            exit(-1);
        }else if(ret == 0){
            continue;
        }else if(ret > 0){
            //说明检测到了有文件描述符的对应的缓冲区的数据发生了改变
            if(fds[0].revents&POLLIN){
                //表示有新的客户端连接进来了
                struct sockaddr_in cliaddr;
                int len = sizeof(cliaddr);
                int cfd = accept(lfd,(struct sockaddr*)&cliaddr,&len);

                //将新的文件描述符加入到集合中
                int i;
                for(i=1;i<1024;i++){
                    if(fds[i].fd==-1){
                        fds[i].fd=cfd;
                        fds[i].events = POLLIN;
                        break;
                    }
                }
                
                //更新最大的文件描述符索引
                nfds = nfds > i ? nfds : i;              
            }

            for(int i=1;i<=nfds;i++){
                if(fds[i].revents&POLLIN){
                    //说明这个文件描述符对应的客户端发来了数据
                    char buf[1024]={0};
                    int len = read(fds[i].fd,buf,sizeof(buf));
                    if(len == -1){
                        perror("read");
                        exit(-1);
                    }else if(len == 0){
                        printf("client closed...\n");
                        close(fds[i].fd);
                        fds[i].fd = -1;
                    }else if(len>0){
                        printf("read buf = %s\n",buf);
                        write(fds[i].fd,buf,strlen(buf)+1);
                    }
                }
            }
        }
    }
    close(lfd);
    return 0;
}

epoll

rbr:红黑树

rdlist:双链表

图解:

epoll API:

#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/epoll.h>

int main(){
    
    //创建socket
    int lfd = socket(PF_INET,SOCK_STREAM,0);
    struct sockaddr_in saddr;
    saddr.sin_port=htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    //绑定
    bind(lfd,(struct sockaddr *)&saddr,sizeof(saddr));

    //监听
    listen(lfd,8);

    //调用epoll_create()创建一个epoll实例
    int epfd = epoll_create(100);

    //将监听的文件描述符相关的检测信息添加到epoll实例
    struct epoll_event epev;
    epev.events=EPOLLIN;
    epev.data.fd = lfd;
    epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);

    struct epoll_event epevs[1024];
    while(1){
        int ret = epoll_wait(epfd,epevs,1024,-1);
        if(ret == -1){
            perror("epoll_wait");
            exit(-1);
        }
        printf("ret = %d\n",ret);

        for(int i=0;i<ret;i++){
            int curfd = epevs[i].data.fd;

            if(curfd == lfd){
                //监听的文件描述符有数据达到,有客户端连接
                struct sockaddr_in cliaddr;
                int len = sizeof(cliaddr);
                int cfd = accept(lfd,(struct sockaddr *)&cliaddr,&len);

                epev.events=EPOLLIN;
                epev.data.fd=cfd;
                epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev);
            }else{
                if(epevs[i].events&EPOLLOUT){
                    continue;
                }
                //有数据到达,需要通信
                char buf[5]={0};
                int len = read(curfd,buf,sizeof(buf));
                if(len==-1){
                    perror("read");
                    exit(-1);
                }else if(len==0){
                    printf("client closed...\n");
                    epoll_ctl(epfd,EPOLL_CTL_DEL,curfd,NULL);
                    close(curfd);
                }else if(len>0){
                    printf("read buf = %s\n",buf);
                    write(curfd,buf,strlen(buf)+1);
                }
            }
        }
    }
    close(lfd);
    close(epfd);
    return 0;
}

#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/epoll.h>
#include<fcntl.h>
#include<errno.h>

int main(){

    //创建socket
    int lfd = socket(PF_INET,SOCK_STREAM,0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    //绑定
    bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));
    
    //监听
    listen(lfd,8);

    //调用epoll_create()创建一个epoll实例
    int epfd = epoll_create(100);

    //将监听的文件描述符相关的检测信息添加到epoll实例中
    struct epoll_event epev;
    epev.events=EPOLLIN;
    epev.data.fd = lfd;
    epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);

    struct epoll_event epevs[1024];
    
    while(1){
        int ret = epoll_wait(epfd,epevs,1024,-1);
        if(ret == -1){
            perror("epoll_wait");
            exit(-1);
        }
        
        printf("ret = %d\n",ret);

        for(int i=0;i<ret;i++){
            int curfd = epevs[i].data.fd;

            if(curfd==lfd){
                //监听的文件描述符有数据到达,有客户端连接
                struct sockaddr_in cliaddr;
                int len = sizeof(cliaddr);
                int cfd = accept(lfd,(struct sockaddr*)&cliaddr,&len);

                //设置cfd属性非阻塞
                int flag = fcntl(cfd,F_GETFL);
                flag |= O_NONBLOCK;
                fcntl(cfd,F_SETFL,flag);

                epev.events = EPOLLIN|EPOLLET;//设置边沿触发
                epev.data.fd = cfd;
                epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev);
            }else{
                if(epevs[i].events & EPOLLOUT){
                    continue;
                }

                //循环读取所有数据
                char buf[5];
                int len = 0;
                while((len = read(curfd,buf,sizeof(buf))>0)){
                    //打印数据
                    write(STDOUT_FILENO,buf,len);
                    write(curfd,buf,len);
                }
                if(len==0){
                    printf("client closed....\n");
                }else if(len == -1){
                    if(errno == EAGAIN){
                        printf("data over.....\n");
                    }else{
                        perror("read");
                        exit(-1);
                    }
                }
            }
        }
    }
    close(lfd);
    close(epfd);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值