IO多路复用
如何解决同时“读鼠标”和“读键盘”的问题
- 多进程实现
- 多线程
- 将“读鼠标”和“读键盘”设置为非阻塞实现
- 多路IO
有关多路IO
多路IO的工作原理
- 使用多路IO时,不需要多进程、多线程以“多线任务”方式实现,也不需要用到非阻塞,那么多路IO的实现原理又是什么?
- 我们以阻塞读为例,来讲解多路IO的原理。
- 图:
如果是阻塞写的话,需要将文件描述符加入写集合,不过我们说过对于99%情况,写操作不会阻塞,所以一般情况下对于写来说,使用多路IO没意义。
注意:对于多路IO来说,只有操作阻塞的fd才有意义,如果文件描述符不是阻塞的,使用多路IO没有意义。
多路IO有什么优势
比如以同时读写鼠标、读键盘为例,如果使用。
- 多进程实现
- 开销很大的,所以不建议这么做。
- 非阻塞方式
- cpu空转,很消耗cpu资源,不建议。
- 多线程
- 常用方法
- 多路IO
- 使用多路IO时,多路IO机制由于在监听时如果没有动静的话,监听会休眠,因此开销也很低,相比多进程和非阻塞来说,多路IO机制也是不错的方式。
select和poll
多路IO有两种实现方式,分别是poll和select,其中select会比poll更常用些。
多路IO之select机制
select函数
-
函数原型
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);-
功能
监听集合中的描述符有没有动静,如果没有动静就阻塞。
如果有动静就成功返回,返回值为集合中有动静的fd的数量
什么是有动静?
比如以读为例,如果fd有数据来了需要被read时,这就是动静。
-
参数
- nfds:readfds、writefds、exceptfds这三个集合中值最大的那个描述符+1,用以说明需要关心描述符的范围,这个范围必须覆盖所有集合中的文件描述符。
- 比如“读集合”中包含0,3,6这三个文件描述符,写集合和异常集合为NULL(没有)。
- nfds == 6+1
- 表示监听范围包含7个描述符,描述符因为是从0算起的,所以监听范围所包含的描述符为
- 0、1、2、3、4、5、6
- 疑问:集合中只包含了0、3、6三个,但是为什么需要监听的有这么多,只能说人家select机制就是这么实现的,这个没办法。
-
readfds、writefds、exceptfds:读、写、异常集合。
-readfds:读集合,放读会阻塞的文件描述符
**-**writefds:写集合,放写会阻塞的文件描述符
-exceptfds:放会异常出错的文件描述符
常用的是读集合,写集合和异常集合基本用不到,所以这里不做介绍,写、异常集合不用时就写NULL
-至于如何将文件描述符放入集合中,我们使用如下带参宏来实现。
void FD_CLR(int fd, fd_set *set); //将fd从集合set中清除,这个宏不需要
int FD_ISSET(int fd, fd_set *set); //判断是不是set中的fd有动静
void FD_SET(int fd, fd_set *set); //将fd放到集合set中
void FD_ZERO(fd_set *set); //将整个集合全部清空 -
比如:
-
fd_set readfds; //定义一个读集合
FD_ZERO(&readfds);//先将集合全部清空
FD_SET(fd, &readfds);//将fd放到readfds集合中
5.timeout:用于设置阻塞超的时间
select函数监听集合时,如果没有任何动静的话就阻塞(休眠)。
如果timeout被设置为NULL的话,select会永远阻塞下去,直到被信号中断或者集合中的某些文件描述符有动静了。
如果你不想休眠太久的话,就可以设置超时时间,如果时间到了但是集合中的fd没有任何动静。
select就返回,然后不再阻塞,超时时的返回的值为0。
成员如下:
struct timeval {
long tv_sec; /*seconds(秒)*/
long tv_usec; /*microseconds(微妙)*/
};
tv_sec:设置秒
tv_usec:设置微妙
时间精度为微秒,也就是说可以设置一个精度为微妙级别的超时时间。
由于select函数有超时功能,实际上可以使用select模拟出一个微秒级精度的定时器,大家下去后可以自己去网上查阅资料研究下怎么实现?
可以熟悉select函数
返回值:
失败返回-1,errno被设置。
select调用出错的情况有很多种,比如select在阻塞时被信号唤醒从而导致出错返回。
errno被设置为EINTR错误号,这个错误号表示函数是被信号中断而出错返回的。
如果不想被信号打断
- 我们可以自己忽略,屏蔽这些信号
- 手动重启select的调用
技巧来的
label:
ret = select(...);
if(ret == -1 && errno == EINTR) goto label;
else if(ret == -1) print_err(...);
0:超时时间并且集合中没有一个描述符有响应时,就返回。
大于0:集合中fd有动静时,函数返回有动静的文件描述符的数量。