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

目录

一、基本概念

1.1.什么是I/O多路复用?

1.2.为什么要有I/O多路复用?

1.3.有哪些I/O复用技术?

二、应用及原理

2.1.select

2.1.1.基本原理

2.1.2.接口及基本使用

2.2.poll

2.2.1.基本原理

2.2.2.接口及基本使用

2.3. epoll

2.3.1. 基本原理

2.3.1.1. 水平触发

2.3.1.2. 边沿触发

2.3.1.3. 触发模式的选择

2.3.2. 接口及基本使用

一、基本概念

1.1.什么是I/O多路复用?

I/O多路复用,是指一个执行单元,同时处理多个关注的I/O事件。

1.2.为什么要有I/O多路复用?

在需要处理多个I/O事件时,往往I/O处理之间是相互阻塞的,如果只依赖于多进程或者多线程技术,则会引入切换和管理的开销。因此,需要I/O复用技术,使得每一个执行单元,有批量处理多个已激活的I/O事件的能力。

1.3.有哪些I/O复用技术?

select、poll、epoll

二、应用及原理

2.1.select

2.1.1.基本原理

用户调用select将关注的文件描述符通过fd_set拷贝传递给内核,由内核遍历集合中是否有文件满足读写要求,并对满足要求的文件打上标记,再把标记后的集合拷贝传递给用户,由用户进行遍历识别处理。

select使用固定长度的bitsmap表示文件描述符机合,所支持的文件描述符个数有限制。Linux系统中由内核中的FD_SETSIZE限制,默认最大值为1024。

2.1.2.接口及基本使用

/*

maxfd:文件描述符的范围,比待监控的最大文件描述符加1。

readfds:指向fd_set结构的指针,是要监控的读类型的文件描述符集合。

writefds:指向fd_set结构的指针,是要监控的写类型的文件描述符集合。

errorfds:指向fd_set结构的指针,是用来监视文件错误异常的文件描述符集合。

timeout:select函数的超时时间,这个参数至关重要,它可以使select处于三种状态。
    1,若将NULL以形参传入,即不传入时间结构,则select一直置于阻塞状态,直到监控到文件描述符            
       集合中某个文件描述符发生变化为止;
    2,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返 
       回继续执行,文件无变化返回0,有变化返回一个正值;
    3,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事 
       件到来就返回了,否则在超时后返回0。

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


FD_CLR(int fd, fd_set *fdset);// 用来清除描述符集合fdset中的描述符fd

FD_SET(int fd, fd_set *fdset);// 用来将描述符fd添加到描述符集合fdset中

FD_ISSET(int fd, fd_set *fdset);// 用来检测描述符集合fdset中的描述符fd是否发生了变化

FD_ZERO(fd_set *fdset);// 用来清除描述符集合fdset


int fd = open(filePath, O_RDWR | O_NONBLOCK);

fd_set myfdset;
FD_ZERO(&myfdset);

FD_SET(fd, &myfdset);

struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;

ret = select(fd+1, &myfdset, NULL, NULL, &timeout);
if (-1 == ret)
{
    printf("no read");
}
else if (0 == ret)
{
    printf("timeout");
}
else
{
    if (FD_ISSET(fd, &myfdset))
    {
        // ....
    }
}

2.2.poll

2.2.1.基本原理

poll和select原理相似,只是poll采用链表的形式存储关注的文件描述符,解除了select文件描述符数量的限制,但在时间复杂度上没有区别。

2.2.2.接口及基本使用

/*
fds:指向一个结构体数组的首个元素的指针,每个数组元素都是一个 struct pollfd 结构,用于指定检测某个给定的 fd 的条件;

nfds:参数 fds 结构体数组的长度,nfds_t 本质上是 unsigned long int

timeout:表示 poll 函数的超时时间,单位为毫秒。

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

struct pollfd{
    int fd;    // 关注的文件描述符
    short events;    // 关心的事件, POLLIN/POLLRDNORM...
    short revents;   // 检测后得到的事件
};


char buff[100];
int fd = open(filePath, O_RDONLY | O_NONBLOCK);


struct pollfd plfd[1];
plfd[0].fd = fd;
plfd[0].events = POLLIN;

int n = poll(plfd, 1, 100);

if (n < 0)
{
    printf("err");
}
else if (0 == n)
{
    printf("timeout");
}
else
{
    if (plfd[0].revents & POLLIN)
    {
        read(plfd[0].fd, buff, sizeof(buff));
    }
}

2.3. epoll

2.3.1. 基本原理

epoll使用一个额外的文件描述符,来唯一标识内核中的这个事件表,事件表使用红黑树来维护用户想监控的文件描述符,用户仅需要在添加文件描述符的时候传入一次文件描述符。epoll使用事件驱动的机制,维护就绪链表来记录就绪事件,当某个文件有事件发生时,通过回调函数将文件描述符加入就绪列表中,用户调用epoll_wait()获取有事件发生的文件描述符个数。

2.3.1.1. 水平触发

当监控的文件有事件发生时,只要还没完成全部的事件处理,事件就还会一直触发。select/poll就只有水平触发模式。

2.3.1.2. 边沿触发

当监控的文件有事件发生时,事件只会触发一次,用户需要一次性完成所有的事件处理。

2.3.1.3. 触发模式的选择

边沿触发可以有效减少epoll_wait()的系统调用次数,在处理大数据量的I/O时较有优势,比如服务器请求处理;水平触发则适合小数据量但需要保证数据完整性的场景,比如电力电气检测。

边沿触发一般配合非阻塞I/O使用,因为边沿触发需要循环进行I/O处理,如果使用阻塞I/O,当得不到处理数据时,会阻塞在数据处理的系统调用中,导致操作无法往下执行

2.3.2. 接口及基本使用

// 创建额外的文件描述符,来唯一标识内核中的内核事件表(eventpoll对象)
// 返回epollfd
int epoll_create(int size); // size参数现在并不生效, 只是通知内核如何划分数据结构大小

/* 
    功能: 往事件表中添加或者删除关注的文件描述符
    返回值: 成功返回0, 不成功返回-1
    op:指定操作
        EPOLL_CTL_ADD:注册新的fd到epfd中;
        EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
        EPOLL_CTL_DEL:从epfd中删除一个fd;

    struct epoll_event {
        __uint32_t events; // epoll事件, EPOLLIN/EPOLLPRI/EPOLLOUT...
        epoll_data_t data; // 用户数据
    };

    typedef union epoll_data{
        void* ptr; // 指定与fd相关的用户数据
        int fd; // 指定事件所从属的目标文件描述符
        uint32_t u32;
        uint64_t u64;
    }epoll_data_t;
*/ 
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 


/* 
    功能: 等待一组文件描述符上的事件
    返回值: 成功事件的数目, 失败返回-1
    events:用来记录被触发的events(结构参考epoll_ctl),其大小受制于maxevents
    maxevents: 设定最多监听多少事件, 一般设定为65535
    timeout: 超时时间设置
*/
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);



int fd = open(filePath, O_RDONLY | O_NONBLOCK);
int epfd = epoll_create(EPOLL_SIZE);

struct epoll_event ev;
ev.data.fd = fd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);

    
char buff[1024];
struct epoll_event events[EPOLL_SIZE];

while (1)
{
    int epoll_events_count = epoll_wait(epfd, events, EPOLL_SIZE, -1);
    if (epoll_events_count < 0)
    {
        perror("epoll failed");
        break;
    }
    for (int i=0;i < epoll_events_count;i++)
    {
        if (events[i].data.fd==fd && (events[i].events & EPOLLIN))
        {
            read(events[i].data.fd, buff, sizeof(buff));
        }
    }

}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦想是优秀社畜

您的打赏是对我最大的鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值