多路复用io简单总结

在没有多路复用IO之前,对于多路IO请求,一般只有阻塞与非阻塞IO两种方式

 阻塞IO :需要结合多进程/多线程,每个进程/线程处理一路IO

缺点:客户端越多,需要创建的进程/线程越多,相对占用内存资源较多

非阻塞IO: 单进程可以处理,但是需要不断检测客户端是否发出IO请求,需要不断占用cpu,消耗 cpu 资源

多路复用IO简介

本质上就是通过复用一个进程来处理多个IO请求 本质上就是通过复用 个进程来处理多个IO请求 基本思想:由内核来监控多个文件描述符是否可以进行I/O操作,如果有就绪的文件描述符,将结果 告知给用户进程,则用户进程在进行相应的I/O操作

目前在Linux系统有三种多路复用I/O的方案

1. select方案

2. poll方案

3. epoll方案

IO—select

设计思想

通过单进程创建一个文件描述符集合,将需要监控的文件描述符添加到这个集合中

由内核负责监控文件描述符是否可以进行读写,一旦可以读写,则通知相应的进程进行相应的I/O操作、

select多路复用I/O在实现时主要是以调用 select 函数来实现

函数使用

函数原型

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

函数功能

监控一组文件描述符,阻塞当前进程,由内核检测相应的文件描述符是否就绪,一旦有文件描述符就绪, 将就绪的文件描述符拷贝给进程,唤醒进程处理

函数参数

nfds:最大文件描述符加1

readfds:读文件描述符集合的指针

writefds:写文件描述符集合的指针

exceptfds:其他文件描述符集合的指针

timeout:超时时间结构体变量的指针

函数返回值

成功:返回已经就绪的文件描述符的个数。

如果设置timeout,超时就会返回0

失败:-1,并设置errno

超时时间的说明

如果timeout之后,文件描述符集合中没有任何就绪的文件描述符,select函数就会返回0 超时之后,timeout会被select函数修改,表示超时时间已经使用完。

如果想继续使用超时时间,需要备份之前的struct timeval 超时之后,表示没有就绪的文件描述符,此时文件描述符集合被赋值为空

因此,需要将之前的文件描述符集合进行备份

例子

使用 select 监听有名管道,当有名管道有数据时,读取数据并打印

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define PATH  "/home/linux/name_pipe"
#define FD 0
int main(){
    int fd = open(PATH,O_RDONLY);
    printf("fd=%d\n",fd);
    //读文件描述符集合
    fd_set readfds;
    //清空读文件描述符集合
    FD_ZERO(&readfds);
    //初始化读文件标识符
    FD_SET(fd,&readfds);
    FD_SET(FD,&readfds);
    //备份读文件标识符
    fd_set readfds_bak ;
    //设置超时时间
    struct timeval timeout= {.tv_sec=3,.tv_usec=0};
    //备份时间
    struct timeval timeout_bak;
    int ret = 0;
    //循环遍历
    while(1){
        readfds_bak = readfds;
        timeout_bak = timeout;
        ret = select(fd+1,&readfds_bak,NULL,NULL,&timeout_bak);    
        if(ret == -1){
            perror("select\n");
            exit(EXIT_FAILURE);
        }else if(ret == 0){//超时
            printf("timeout\n");
        }else if(ret > 0){//对应的文件标识符就绪
            for(int i = 0;i<ret;i++){
                if(FD_ISSET(0,&readfds_bak)){
                    char buf[128]={0};
                    memset(buf,0,sizeof(buf));
                    fgets(buf,sizeof(buf),stdin);
                    printf("buf=%s\n",buf);
                }
                if(FD_ISSET(fd,&readfds_bak)){
                    char buff[128]={0};
                    memset(buff,0,sizeof(buff));
                    ssize_t bytes = read(fd,buff,sizeof(buff));
                    if(bytes > 0){
                        printf(" wirte buff:%s\n",buff);
                    }            
                }
            }
        }
    }
    return 0;
}

IO-poll

基本原理

多路复用poll的方式与select多路复用原理类似,但有很多地方不同,下面是具体的对比

在应用层是以结构体struct pollfd数组的形式来进行管理文件描述符,在内核中基于链表对数组进 行扩展;select方式以集合的形式管理文件描述符且最大支持1024个文件描述

poll将请求与就绪事件通过结构体进行分开

select将请求与就绪文件描述符存储在同一个集合中,导致每次都需要进行重新赋值才能进行下一 次的监控

在内核中仍然使用的是轮询的方式,与 select 相同,当文件描述符越来越多时,则会影响效率

函数使用

poll多路复用实现主要调用 poll 函数

函数原型

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

函数功能

监控多个文件描述符的变化

函数参数

fds:sturct pollfd结构体指针

nfds:fds结构体的数量

timeout:超时时间,单位为ms

参数相关说明

1. struct pollfd 结构体说明

struct pollfd {    

        int   fd;         /* file descriptor */  

          short events;     /* requested events */  

          short revents;   /* returned events */

};

例子

使用 poll 监听有名管道,当有名管道有数据时,读取数据并打印

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define PATH  "/home/linux/name_pipe"

int main(){
    int wfd = open(PATH,O_RDONLY);
    printf("wfd=%d\n",wfd);
    struct pollfd pfd[2];
    pfd[0].fd = 0;
    pfd[0].events = POLLIN;
    pfd[1].fd = wfd;
        pfd[1].events = POLLIN;
    nfds_t nfds = 2;
    int ret;
    while(1){
        ret = poll(pfd,nfds,1000);
        if(ret == -1){
            perror("poll\n");
            exit(EXIT_FAILURE);
        }else if(ret == 0){
            printf("timeout\n");
        }else if(ret >0){
            for(int i = 0;i<nfds;i++){
                if(pfd[i].revents == POLLIN){
                                    if(pfd[i].fd == 0){
                                            char buf[128]={0};
                                            memset(buf,0,sizeof(buf));
                                            fgets(buf,sizeof(buf),stdin);
                                            printf("buf=%s\n",buf);
                                    }
                     if( pfd[i].fd == wfd){
                                                char buff[128]={0};
                                            memset(buff,0,sizeof(buff));
                                            ssize_t bytes = read(wfd,buff,sizeof(buff));
                                            if(bytes > 0){
                                                    printf(" wirte buff:%s\n",buff);
                                            }
                                    }
                }
            }
        }
    }
    return 0;
}

IO—epoll

epoll 基本原理

epoll相对于select与poll有较大的不同,主要是针对前面两种多路复用 IO 接口的不足

select/poll的不足:

select 方案使用数组存储文件描述符,最大支持1024个

select 每次调用都需要将文件描述符集合拷贝到内核中,非常消耗资源

poll 方案解决文件描述符存储数量限制问题,但其他问题没有得到解决

select / poll 底层使用轮询的方式检测文件描述符是否就绪,文件描述符越多,则效率越低

epoll优点:

epoll底层使用红黑树,没有文件描述符数量的限制,并且可以动态增加与删除节点,不用重复拷贝 epoll底层使用callback机制,没有采用遍历所有描述符的方式,效率较高

函数使用

epoll创建需要调用epoll_create函数,用于创建epoll实例

函数原型

int epoll_create(int size);

函数功能

创建一个epoll实例,分配相关的数据结构空间

函数参数

size:需要填一个大于0的数,从Linux 2.6.8开始,size参数被忽略

函数返回值

成功:返回epoll文件描述符

失败:返回-1,并设置errno

epoll_ctl函数

epoll控制函数主要用于文件描述符集合的管理,包括增加、修改、删除等操作。

函数原型

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

函数参数

epfd:epoll 实例

op:epoll 操作命令字

EPOLL_CTL_ADD:在epoll实例中添加新的文件描述符(相当于向红黑树中添加节点),并将事件与 fd关联

EPOLL_CTL_MOD:更改与目标文件描述符fd相关联的事件

EPOLL_CTL_DEL:从epoll实例中删除目标文件描述符fd ,事件参数被忽略

epoll_data是一个共用体,主要使用 fd 成员用于存储文件描述符

epoll 等待函数

epoll 等待事件发生(关联的文件描述符就绪),这里调用 epoll_wait 函数

函数原型

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

函数功能

等待文件描述符关联的事件发生

函数参数

epfd:epoll实例对象

events:存储就绪集合的数组的地址

maxevents:就绪集合的最大值

timeout:超时时间

函数返回值

成功:返回就绪的文件描述符数量,超时返回0

失败:返回-1,并设置errno

例子

使用epoll 监听有名管道,当有名管道有数据时,读取数据并打印

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define PATH  "/home/linux/name_pipe"

int main(){
    int wfd = open(PATH,O_RDONLY);
    printf("wfd=%d\n",wfd);
    //创造epoll
    int efd = epoll_create(2);
    if(efd == -1){
        perror("epoll_create\n");
        exit(EXIT_FAILURE);
    }
    //将描述符加入epoll
    struct epoll_event ev[2];
    ev[0].data.fd = 0;
    ev[0].events =  EPOLLIN; 
    ev[1].data.fd = wfd;
        ev[1].events =  EPOLLIN;
    int ret;
    ret = epoll_ctl(efd,EPOLL_CTL_ADD,wfd,ev+1);
    if(ret == -1){
        perror("epoll_ctl\n");
        exit(EXIT_FAILURE);
    }
    ret = epoll_ctl(efd,EPOLL_CTL_ADD,0,ev);
        if(ret == -1){
                perror("epoll_ctl\n");
                exit(EXIT_FAILURE);
        }
    //就绪集合
    struct epoll_event events[10];
    while(1){
         int res = epoll_wait(efd,events,10,1000);
                if(res == -1)
                {
                        perror("epoll_wait");
                        exit(EXIT_FAILURE);
                }
                else if(res == 0)
                {
                  printf("timeout\n");                  
        }else if(res >0){
                        for(int i = 0;i<res;i++){
                if(events[i].events ==  EPOLLIN ){
                                    if(events[i].data.fd==0){
                                            char buf[128]={0};
                                            memset(buf,0,sizeof(buf));
                                            fgets(buf,sizeof(buf),stdin);
                                            printf("buf=%s\n",buf);
                                    }
                                     if( events[i].data.fd == wfd){
                                            char buff[128]={0};
                                            memset(buff,0,sizeof(buff));
                                            ssize_t bytes = read(wfd,buff,sizeof(buff));
                                            if(bytes > 0){
                                                    printf(" wirte buff:%s\n",buff);
                                            }
                                    }
                            }
            }
        }
    }
    return 0;
}

有名管道代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

#define PATH  "/home/linux/name_pipe"
int main()
{
/*    int pipe=mkfifo(PATH,0666);//如果管道未创建,创建管道
    if(-1==pipe){
        perror("mkfifo");
        exit(EXIT_FAILURE);
    }*/
    int fd=open(PATH,O_WRONLY);
    if(-1==fd){
        perror("open");
        exit(EXIT_FAILURE);
    }
    char buf[128]={0};
    while(1){
            memset(buf,0,sizeof(buf));
            fgets(buf,sizeof(buf),stdin);
            ssize_t wbetys=write(fd,buf,sizeof(buf));
                    if(wbetys == -1){
                            perror("write");
                            close(fd);
                            exit(EXIT_FAILURE);
            }
            char buff[128] = "exit";
            if(strncmp(buf,buff,4) == 0){
                break;
            }
        }
    close(fd);    
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值