I/O多路复用(select、poll、epoll)

基本思想

1、先构造一张有关文件描述符的表,然后使用我们的select、poll、epoll函数

2、我们的应用程序会将这张表复制给内核。

3、内核层初始化表中的需要检测的描述符。

4、当检测到有文件操作时,则立即将文件描述符作为标志并返回给应用程序。

5、应用程序根据内核返回的表来进行相应的I/O操作。

函数原型

select

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

void FD_CLR(int fd, fd_set *set);

int  FD_ISSET(int fd, fd_set *set);

void FD_SET(int fd, fd_set *set);

void FD_ZERO(fd_set *set);

struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };


参数说明:

                nfds: 程序中最大的文件描述符值 + 1(注意:一般我们自定义的文件描述符是从3开始,0,1,2被系统占用为stderr,stdout,stdin)
                readfds:   读操作文件描述符的集合.
                writefds:  写操作文件描述符的集合.(在线程池当中可能用到)
                exceptfds: 异常文件描述符集合.(基本不会使用)
                timeout:   函数超时检测. 
               
                如果为 NULL 则一直等待,直到有可操作的文件描述符
                比如:2秒的话,表示2秒后如果没有文件描述符可操作则立即返回
                
                返回值:错误 -1 . 
                正确返回select函数检测到的能够执行操作的文件描述符个数.

void FD_CLR(int fd, fd_set *set);
         清除集合表当中的指定文件描述符fd.
int  FD_ISSET(int fd, fd_set *set);
          判断文件描述符是否在表内为1.
void FD_SET(int fd, fd_set *set);
          把文件描述符加入集合
void FD_ZERO(fd_set *set); 
          把集合表中所有文件描符清空

示例:

#include <strings.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{ 
    int fd = open("/dev/input/mice", O_RDONLY);//打开我们Linux下的鼠标文件
    if(fd == -1)
    {
        perror("open");
        return -1;
    }
    char buf[32] = {0};

    fd_set rfds, t_fds;//创建两张表,一张用于操作,一张用于维持原来的状态
    FD_ZERO(&rfds);   //把集合中的所有文件描述清空
    FD_SET(0, &rfds);//最开始我们将所有的状态都置为0
    FD_SET(fd, &rfds);//将我们的鼠标文件描述加入到文件描述表中
    int max_fd = fd + 1;
    int i = 0;
    int ret = 0;

    struct timeval t_val = {3, 0};//初始化时间

    while(1)
    {
        t_fds = rfds;
        t_val.tv_sec = 3;
        t_val.tv_usec = 0;
        if((ret = select(max_fd, &t_fds, NULL, NULL, &t_val)) <= 0)//调用select函数
        {
            if(ret == 0)
            {
                printf("timeout-----------\n");
                //return -1;
                continue;
            }
            else
            {
                perror("select");
                return -1;
            }
        }

        for(i = 0; i < max_fd; i++)//逐一检测从0到最大的fd中的可操作文件描述符,或者进行我们想要的操作
        {
            if(1 == FD_ISSET(i, &t_fds))
            {
                if(i == 0)//i=0时从键盘输入数据
                {
                    memset(buf, 0, sizeof(buf));
                    fgets(buf, sizeof(buf), stdin);
                    buf[strlen(buf) - 1] = '\0';
                    printf("%s\n", buf);
                }
                else if(i == fd)//等于我们的fd时,获取鼠标的坐标
                {
                    memset(buf, 0, sizeof(buf));
                    read(fd, buf, sizeof(buf));
                    printf("%d--%d--%d\n", buf[0], buf[1], buf[2]);
                }
            }
        }

    }
    return 0;

select函数的缺点:

        1、文件描述符有上限,Linux系统一般默认1024个.
        2、监听表与返回表是同一张表,没有实现分离.
        3、文件描述符表需要被复制到内核,又需要从内核复制到用户区.
        4、需要循环线性的监测已就绪的IO操作,会出现无意义的循环.

poll

函数原型:

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


struct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
           };

参数说明:

        fds为系统指定的结构体数组(传首地址),nfds最大的文件描述符+1,timeout设置的超时,其值为负整数或正整数

示例:

#include <strings.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <poll.h>
#include <fcntl.h>
#include <unistd.h>
#define FDSIZE 3002//最大的文件描述符个数
int main(int argc, char *argv[])
{ 
    struct pollfd fd_arr[FDSIZE];//定义结构体类型
    int m_fd = open("/dev/input/mice", O_RDONLY);
    if(m_fd == -1)
    {
        perror("open");
        return -1;
    }
    /*init*/
    bzero(fd_arr, FDSIZE);//清空结构体

    int i = 0;
    for(i = 0; i < FDSIZE; i++)//将结构体里面的fd全部置为-1
    {
        fd_arr[i].fd = -1;
    }

    int maxpos = 0;
    fd_arr[maxpos].fd = 0;
    fd_arr[maxpos].events = POLLIN;//pollon为系统定义的宏,意思是读操作
    maxpos++;
    
    fd_arr[maxpos].fd = m_fd;
    fd_arr[maxpos].events = POLLIN;
    maxpos++;

    char buf[32] = {0};
    while(1)
    {
        if(-1 == poll(fd_arr, FDSIZE, 0))//调用poll函数
        {
            perror("poll");
            return -1;
        }
        for(i = 0; i < maxpos; i++)//逐一检测fd的状态,实现我们想要进行的操作
        {
            if(fd_arr[i].fd == -1)
                continue;
            if(fd_arr[i].revents == POLLIN)
            {
                if(fd_arr[i].fd == 0)
                {
                    fgets(buf, sizeof(buf), stdin);
                    buf[strlen(buf)-1] = '\0';
                    printf("buf=%s\n", buf);
                }
                else if(fd_arr[i].fd == m_fd)
                {
                    memset(buf, 0, sizeof(buf));
                    read(m_fd, buf, sizeof(buf));
                    printf("%d\n", buf[0]);
                }
            }
        }
    }

    return 0;
} 

poll的优缺点:

优点:

1、文件描述符无上限(使用链表进行管理),但是受硬件限制. ​ 2、监听表与返回表实现分离.

缺点:

1、文件描述符表需要被复制到内核,又需要从内核复制到用户区.

2、需要循环线性的监测已就绪的IO操作,会出现无意义的循环.

epoll

epoll跟上面两个函数,稍微有点区别,它的函数包括了create,ctl,wait等,可以说是一个函数族

函数原型:

int epoll_create(int size);

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

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

  函数和参数说明

epoll_create:返回的是一个引用epoll后的文件描述符,该文件描述符为后面所有文件描述符的接口,这跟epoll的实现有关系,相当于创建一个棵树,size为树的节点个数,

epoll_ctl:文件描述符控制函数(把文件描述符添加到树上、或者从数上删除以及修改)

              epfd: epoll_create 函数返回的树操作句柄
              op:    要对文件描述符执行的操作.
                   EPOLL_CTL_ADD: 在树中添加文件描述符
                   EPOLL_CTL_MOD: 修改
                   EPOLL_CTL_DEL: 删除.       
            fd:  要操作的文件描述符.
            event: 一个结构体的地址.
            typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;
           struct epoll_event {
               uint32_t     events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };
           events: 文件描述符需要的操作
                      例如:读:EPOLLIN
                                 写:EPOLLOUT
                                 异常:EPOLLERR
           data是一个联合体: 
                      通常使用第二个成员fd.代表需要检测的文件描述符 
           返回值: 正确返回 0  错误返回 -1

epoll_wait:系统调用等待文件描述epfd引用的实例

        epfd:      epoll_create 函数返回的树操作句柄
        events:    结构体数组的首地址.
        maxevents: 数组中要操作的文件描述符最大个数.(一般和create函数传入的参数一致)
        timeout: 
                -1: 代表一直等待直到有io操作才返回
                0 :不等待立即返回
               >0: 定时的时间 单位毫秒
            返回值:
                正确返回 可操作的文件描述符个数 
                错误返回 -1 

示例:

#include <strings.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <unistd.h>
#define MAXSIZE 2000
int main(int argc, char *argv[])
{ 
    int i = 0;
    int m_fd = open("/dev/input/mice", O_RDONLY);
    if(m_fd == -1)
    {
        perror("open");
        return -1;
    }
    struct epoll_event epfd, lfds[MAXSIZE];//定义操作句柄和结构体数组
    int e_fd = epoll_create(1);
    if(e_fd == -1)
    {
        perror("epoll_create");
        return -1;
    }
    bzero(lfds, sizeof(lfds));
    bzero(&epfd, sizeof(epfd));
    epfd.events = EPOLLIN;//EPOLL系统定义的宏,读操作
    epfd.data.fd = 0;
    if(-1 == epoll_ctl(e_fd, EPOLL_CTL_ADD, 0, &epfd))//将e_fd加入到我们的操作树句柄中
    {
        perror("ctl");
        return -1;
    }

    epfd.events = EPOLLIN;//设置读操作
    epfd.data.fd = m_fd;//将fd设置为我们的鼠标fd
    if(-1 == epoll_ctl(e_fd, EPOLL_CTL_ADD, m_fd, &epfd))
    {
        perror("ctl");
        return -1;
    }
    int nread = 0;
    char buf[32] = {0};

    while(1)
    {
        if((nread = epoll_wait(e_fd, lfds, MAXSIZE, -1)) == -1)//调用epoll_wait函数,并保留我们可操作的文件描述符个数
        {
            perror("epoll_wait");
            return -1;
        }
        printf("fd=%d\n", lfds[0].data.fd);

        for(i = 0; i < nread; i++)//根据自己的需求进行对相应的文件描述符进行操作
        {
            if(lfds[i].events == EPOLLIN)
            {
                if(lfds[i].data.fd == 0)
                {
                    fgets(buf, sizeof(buf), stdin);
                    buf[strlen(buf)-1] = '\0';
                    printf("buf=%s\n", buf);
                }
                else if(lfds[i].data.fd == m_fd)
                {
                    memset(buf, 0, sizeof(buf));
                    read(m_fd, buf, sizeof(buf));
                    printf("%d\n", buf[0]);
                }
            }
        }
    }

    return 0;
} 

epoll函数的优缺点:

优点:

1、文件描述符无上限,但是受硬件限制.

2、监听表与返回表实现分离.

3、文件描述符表采用映射(mmap)机制.

4、利用二叉树(红黑树) 以及一个就绪链表来管理文件描述符,就绪链表只会有就绪的IO 操作(不会有无意义的循环).

缺点:

1、内部数据结构的管理比较麻烦,如果文件描述符少的情况下建议使用select或poll

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值