目录
一、基本概念
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));
}
}
}