目录
IO多路复用
场景假设
假设妈妈有三个孩子,分别不同的房间里睡觉,需要及时获知每个孩子是否醒了,如何做?
- 不停进每个房间看一下:简单,空闲时间还能干点别的,但是很累
- 告诉爸爸,让爸爸帮忙监听,妈妈可以干别的或者睡觉,孩子醒了后,爸爸告诉妈妈哪个孩子需要照顾,及时处理即可:既能得到休息,也能及时获知每个孩子的状态。
处理思想
- 应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;
- 若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;
- 若设置多个进程/线程,分别处理一条数据通路,将新产生进程/线程间的同步与通信问题,使程序变得更加复杂;
- 比较好的方法是使用I/O多路复用技术。其基本思想是:
- 先构造一张监听描述符表(最大1024),然后调用一个函数进入监听状态。
- 当这些文件描述符中的一个或多个有数据时,函数才返回。
- 函数返回时告诉进程哪个描述符已就绪,可以进行I/O操作。
接口
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
功能:
实现IO的多路复用
参数:
nfds:关注的最大的文件描述符+1
readfds:关注的读表
writefds:关注的写表
exceptfds:关注的异常表
timeout:超时的设置
NULL:一直阻塞,直到有文件描述符就绪或出错
时间值为0:仅仅检测文件描述符集的状态,然后立即返回
时间值不为0:在指定时间内,如果没有事件发生,则超时返回0,并清空设置的时间值
struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* 微秒 = 10^-6秒 */
};
返回值:
准备好的文件描述符的个数
-1 :失败:
0:超时检测时间到并且没有文件描述符准备好
注意:
select返回后,关注列表中只存在准备好的文件描述符
void FD_CLR(int fd, fd_set *set); //清除集合中的fd位
int FD_ISSET(int fd, fd_set *set);//判断fd是否在集合中 是--》1 不是---》0
void FD_SET(int fd, fd_set *set);//将fd放入关注列表中
void FD_ZERO(fd_set *set);//清空关注列表
编程步骤
- 把关注的文件描述符放入集合--FD_SET
- 监听集合中的文件描述符--select
- 依次判断哪个文件描述符有数据--FD_ISSET
- 依次处理有数据的文件描述符的数据
以下提到的监听表,都是位表,第几位被置位,那么就代表这个文件描述符需要被监听或者有数据
参考程序
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
//需求:同时监听标准输入和鼠标,不管哪一个有数据,及时处理,并且主进程不能占用太多资源。
int main(int argc, char const *argv[])
{
int fd;
//打开鼠标设备
fd = open("/dev/input/mouse0", O_RDONLY);
if(fd < 0)
{
perror("open err");
return -1;
}
fd_set rdfds;
int ret;
char buf[64] = {
0};
while (1)
{
//1.把关注的文件描述符放入集合--FD_SET
FD_ZERO(&rdfds); //清空关注列表
FD_SET(0, &rdfds); //把标准输入放入到表中进行监听
FD_SET(fd, &rdfds); //把鼠标放入到表中进行监听
memset(buf, 0, 64);
//2.监听集合中的文件描述符--select
ret = select(fd + 1, &rdfds, NULL, NULL, NULL);
if(ret < 0)
{
perror("select err");
return -1;
}
//3.依次判断哪个文件描述符有数据--FD_ISSET
if(FD_ISSET(0, &rdfds)) //判断一下标准输入是否有数据
{
//标准输入有数据,那么就获取它的数据
gets(buf);
printf("stdin = %s\n", buf);
}
if(FD_ISSET(fd, &rdfds)) //判断一下鼠标是否有数据
{
//鼠标有数据,那么就获取它的数据
int len = read(fd, buf, 64);
if(len > 0)
{
printf("read mouse len = %d\n", len);
}
}
}
return 0;
}
总结
阻塞模型:不占用CPU,但是不能同时处理多个设备
非阻塞模型:能同时处理多个设备,但是非常耗费CPU
异步IO/信号驱动IO:完全解放了主线程,有数据会主动通知,异步调用处理方法,但是也不能同时处理多个设备。
IO多路复用:既能同时处理多个设备,又不占用CPU
以上这4种方法没有绝对优劣之分,在不同的场合使用不同的方法。
其它多路复用方案
select
• 一个进程最多只能监听1024个文件描述符 (千级别)
• select每次会清空表,每次都需要拷贝用户空间的表到内核空间,效率低
poll
• 优化文件描述符个数的限制(根据poll函数第一个函数的参数来定,如果监听的事件为1个,则结构体数组的大小为1,如果想监听100个,那么这个结构体数组的大小就为100,由程序员自己来决定)
• poll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
作用:监视并等待多个文件描述符的属性变化
参数:
fds:指向一个结构体数组的第0个元素的指针,每个数组元素都是一个struct pollfd结构,用于指定某个给定的fd的条件
nfds:一共需要监听几个描述符
timeout:等待的超时时间,-1:永远等待,0:立即返回,>0:等待相应的ms
返回值:同select
struct pollfd{
int fd; //文件描述符
short events; //等待的事件--POLLIN、POLLOUT、POLLERR
short revents; //实际发生的事件
};
poll机制参考程序
#include <stdio.h>
#include <poll.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
int fd_mouse;
// 1.创建一个用于保存要监测文件描述符的结构体
struct pollfd fds[2];
// 2.将关心的文件描述符及对应关心事件添加到结构体数据中
fd_mouse = open("/dev/input/mouse0", O_RDONLY);
if (fd_mouse < 0)
{
perror("open mouse2 err.");
return -1;
}
//标准输入放到表里,监听读事件
fds[0].fd = 0;
fds[0].events = POLLIN;
//鼠标放到表里,监听读事件
fds[1].fd = fd_mouse;
fds[1].events = POLLIN;
char buf[32];
int ret;
while (1)
{
if ((ret = poll(fds, 2, -1)) < 0)
{
perror("poll err.");
return -1;
}
else if (ret == 0)
{
printf("timeout .........\n");
}
if (fds[0].revents == POLLIN)
{
fgets(buf, 32, stdin);
printf("recv from stdin:%s\n", buf);
}
if (fds[1].revents == POLLIN)
{
memset(buf, 0, 32);
int ret = read(fd_mouse, buf, 32);
printf("read from mouse len = %d\n", ret);
}
}
close(fd_mouse);
return 0;
}
select() 和 poll() 系统调用的本质一样,poll() 的机制与 select() 类似,与 select() 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是 poll() 没有最大文件描述符数量的限制(但是数量过大后性能也是会下降)。poll() 和 select() 同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
epoll(了解即可)epoll详解
• 监听的最大的文件描述符没有个数限制(理论上,取决与你自己的系统)
• 异步I/O,Epoll当有事件产生被唤醒之后,文件描述符主动调用callback(回调函数)函数直接拿到唤醒的文件描述符,不需要轮询,效率极高
标签:文件,多路复用,int,编程,描述符,fd,IO,include,监听 From: https://blog.csdn.net/ciweic/article/details/145264857