如何解决同时“读鼠标”和“读键盘”的问题
1)多进程实现
2)多线程
3)将“读鼠标”和“读键盘”设置为非阻塞实现
4)多路IO
前三种实现参考高级IO——非阻塞IO
有关多路IO
多路IO的工作原理
使用多路IO时,不需要多进程、多线程以“多线任务”方式实现,也不需要用到非阻塞,那么多路IO的实现原理又是什么呢?我们以阻塞读为例,来讲解多路IO的原理。
1)如果没有动静
说明集合中所有的fd都没有数据,监听会阻塞不过请注意,就算休眠时,监听机制依然工何工作。
2)如果有动静
说明集合中某个或某几个fd有数据来了,哪些fd有数据,然后读数据
如果是阻塞写的话,需要将文件描述符加入写集合,不过我们说过对于99%的情况来说,写操作不会阻塞,所以一般情况下对于写来说,使用多路Io没有意义。
注意:对于多路io来说,只有操作阻塞的fd才有意义,如果文件描述符不是阻塞的,使用多路IO没有意义。
如果是非阻塞的,在监听文件描述符动静时,会认为是有动静的。在检查集合中哪个fd有数据时,非阻塞的fd是没有数据的,于是又循环一边,反反复复死循环。
多路IO有什么优势
比如以同时读写鼠标、读键盘为例,如果使用,
1)多进程实现 开销太大,绝对不建议这么做。
2)非阻塞方式 cpu空转,耗费cpu资源,不建议。
3)多线程 常用方法,不过本小节讲的“多路IO"也是一个不错的方法。
4)多路IO 使用多路IO时,多路IO机制由于在监听时如果没有动静的话,监听会休眠,因此开销也很低,相比多进程和非阻塞来说,多路IO机制也是很不错的方式。
多i路IO 2种实现方式
多路IO有两种实现方式,分别是poll和select,其中select会比poll更常用些。
多路io之select机制
原型
#include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
功能
监听集合中的描述符有没有动静,如果没有动静就阻塞。如果有动静就成功返回,返回值为集合中有动静的fd的数量。
什么是有动静?
答:比如以读为例,如果fd有数据来了需要被read时,这就是动静。
参数
nfds:
nfds = readfds, writefds, exceptfds这三个集合中值最大的那个描述符+1。
nfds用于说明需要关心描述符的范围,这个范围必须覆盖所有集合中的文件描述符。
比如“读集合”中包含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, &readfs);//将fd放到readfds集合中
timeout:
用于设置阻塞超的时间。
select函数监听集合时,如果没有任何动静的话就阻塞(休眠)。如果timeout被设置为NULL的话,select会永远阻塞下去,直到被信号中断或者集合中的某些文件描述符有动静了。如果你不想休眠太久的话,就可以设置超时时间,如果时间到了但是集合中的fd还没有任何动静,select就返回,然后不再阻塞,超时时的返回的值为0。
成员结构如下:
struct timeval { long tv_sec; /* seconds(秒) */ long tv_usec; /* microseconds (微秒)*/ };
tv_sec:设置秒
tv_usec:设置微妙。时间精度为微妙,也就是说可以设置一个精度为微妙级别的超时时间。
由于select函数有超时功能,实际上可以使用select模拟出一个微妙级精度的定时器。代码演示(稍后填坑)
返回值
①-1:说明函数调用失败,errno被设置。
select调用出错的情况有很多种,比如select在阻塞时被信号唤醒从而导致出错返回,errno被设置为EINTR错误号,这个错误号表示函数是被信号中断而出错返回的。
如果不想被信号中断,
1)我们可以自己忽略、屏蔽这些信号
2)手动重启select的调用
lable: ret = select(...); if(ret==-1 && errno==EINTR) goto lable; else if(ret == -1) print_err(...);
②0:超时时间到并且集合中没有一个描述符有响应时,就返回0。
③>0:集合中fd有动静时,函数返回有动静的文件描述符的数量。
代码演示
select每次重新监听时需要重新设置“集合”和“超时时间”,因为每次select监听结束时会清空“集合”和“超时时间”。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <string.h> 5 #include <strings.h> 6 #include <errno.h> 7 #include <sys/time.h> 8 #include <sys/types.h> 9 #include <unistd.h> 10 #include <sys/types.h> 11 #include <sys/stat.h> 12 #include <fcntl.h> 13 14 15 16 17 void print_err(char *str, int line, int err_no) 18 { 19 printf("%d, %s: %s\n", line, str, strerror(err_no)); 20 exit(-1); 21 } 22 23 24 int main(void) 25 { 26 int ret = 0; 27 int mousefd = 0; 28 char buf1[100] = {0}; 29 int buf2 = 0; 30 fd_set readfds; 31 struct timeval timeover; 32 33 mousefd = open("/dev/input/mouse0", O_RDONLY); 34 if(mousefd == -1) print_err("open mouse0 fail", __LINE__, errno); 35 36 37 while(1) 38 { 39 /* 经0和mousefd加入读集合 */ 40 FD_ZERO(&readfds); 41 FD_SET(0, &readfds); 42 FD_SET(mousefd, &readfds); 43 44 /* 设置超时时间 */ 45 timeover.tv_sec = 3; 46 timeover.tv_usec = 0; 47 48 /* select监听:如果集合没有动静就阻塞 */ 49 lable: ret = select(mousefd+1 , &readfds, NULL, NULL, &timeover); 50 if(ret==-1 && errno==EINTR) goto lable; 51 else if(ret == -1) print_err("select fail", __LINE__, errno); 52 else if(ret > 0) 53 { 54 if(FD_ISSET(0, &readfds)) 55 { 56 bzero(buf1, sizeof(buf1)); 57 ret = read(0, buf1, sizeof(buf1)); 58 if(ret > 0) printf("%s\n", buf1); 59 } 60 if(FD_ISSET(mousefd, &readfds)) 61 { 62 bzero(&buf2, sizeof(buf2)); 63 ret = read(mousefd, &buf2, sizeof(buf2)); 64 if(ret > 0) printf("%d\n", buf2); 65 } 66 } 67 else if(ret == 0) 68 { 69 printf("time out\n"); 70 } 71 } 72 73 return 0; 74 }
多路io 之 poll机制
原型
#include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能
监听集合有没有动静,如果没有动静就阻塞;如果有动静就成功返回,返回值为集合中有动静的fd的数量。
参数
fds:
这个参数写为struct pollfd fds[]更好理解些,第一个参数要求传递一个struct pollfd结构体数组。这个数组就相当于select的文件描述符集合,只不过select是使用fd_set来定义的,而poll的集合是一个数组。
struct pollfd的成员:
struct pollfd { int fd; :文件描述符 short events; :设置我们希望发生的事件,比如读事件,这个需要我们自己设置 short revents; :实际发生的事件,比如读事件,由poll机制自己设置 };
struct pollfd fds[2]; fds[0].fd = 0; fds[0].events = POLLIN;//读事件(输入事件) fds[1].fd = 3; fds[1].events = POLLIN;//读事件(输入事件)
poll监听时如果没有动静就阻塞,有动静就不再阻塞,返回有动静的fd的数量。
如何知道是那些fd有动静?
答:如果文件描述符“发生的事件”==“实际事件”,就说明希望的事件来了,就是对fd进行相应的“读或写”操作。
使用举例:
if(fds[1].events == fds[1].revents)//如果相等,就说明fds[1].fd有动静。
{
//读写fds[1].fd
}
nfds:数组的元素个数。
timeout:超时时间,如果写的是
(a)-1:不设置超时,如果集合没有动静就一直阻塞下去,直到poll函数被信号中断(唤醒)或者集合有动静为止
(b)非-1值:比如3000(3000微妙),表示将超时时间设置为3秒,也就是说poll超时时间的单位时微妙。
返回值
①返回-1:说明函数调失败,errno被设置。
如果是被信号中断从而导致出错返回-1时,errno被设置为EINTR
如果不想被中断,要么重启poll的调用,要么忽略或者屏蔽这些信号。
②0:超时时间到,而且没有文件描述符有动静。
③>0:返回有响应的文件描述符的数量。
代码演示
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <string.h> 5 #include <strings.h> 6 #include <errno.h> 7 #include <sys/time.h> 8 #include <sys/types.h> 9 #include <unistd.h> 10 #include <sys/types.h> 11 #include <sys/stat.h> 12 #include <fcntl.h> 13 #include <poll.h> 14 15 void print_err(char *str, int line, int err_no) 16 { 17 printf("%d, %s: %s\n", line, str, strerror(err_no)); 18 exit(-1); 19 } 20 21 int main(void) 22 { 23 int ret = 0; 24 int mousefd = 0; 25 char buf1[100] = {0}; 26 int buf2 = 0; 27 struct pollfd fds[2]; 28 29 mousefd = open("/dev/input/mouse0", O_RDONLY); 30 if(mousefd == -1) print_err("open mouse0 fail", __LINE__, errno); 31 32 fds[0].fd = 0; 33 fds[0].events = POLLIN; 34 35 fds[1].fd = mousefd; 36 fds[1].events = POLLIN; //期望的事件 37 38 while(1) 39 { 40 lable: ret = poll(fds, 2, 3000); 41 if(ret==-1 && errno==EINTR) goto lable; //重启系统调用 42 else if(ret == -1) print_err("poll fail", __LINE__, errno); 43 if(ret > 0) 44 { 45 if(fds[0].events == fds[0].revents) 46 { 47 bzero(buf1, sizeof(buf1)); 48 ret = read(fds[0].fd, buf1, sizeof(buf1)); 49 if(ret > 0) printf("%s\n", buf1); 50 } 51 if(fds[1].events == fds[1].revents) 52 { 53 bzero(&buf2, sizeof(buf2)); 54 ret = read(fds[1].fd, &buf2, sizeof(buf2)); 55 if(ret > 0) printf("%d\n", buf2); 56 } 57 } 58 else if(ret == 0) printf("tome out\n"); 59 } 60 61 return 0; 62 }