高级IO——多路IO

如何解决同时“读鼠标”和“读键盘”的问题

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);
View Code

功能

监听集合中的描述符有没有动静,如果没有动静就阻塞。如果有动静就成功返回,返回值为集合中有动静的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集合中
View Code

timeout

用于设置阻塞超的时间。

select函数监听集合时,如果没有任何动静的话就阻塞(休眠)。如果timeout被设置为NULL的话,select会永远阻塞下去,直到被信号中断或者集合中的某些文件描述符有动静了。如果你不想休眠太久的话,就可以设置超时时间,如果时间到了但是集合中的fd还没有任何动静,select就返回,然后不再阻塞,超时时的返回的值为0。

成员结构如下:

struct timeval 
{
    long    tv_sec;  /* seconds(秒) */
    long    tv_usec; /* microseconds (微秒)*/
};
View Code

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(...);
View Code

②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 }
View Code

多路io 之 poll机制

原型

#include <poll.h>                
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
View Code

功能

监听集合有没有动静,如果没有动静就阻塞;如果有动静就成功返回,返回值为集合中有动静的fd的数量。

参数

fds

这个参数写为struct pollfd fds[]更好理解些,第一个参数要求传递一个struct pollfd结构体数组。这个数组就相当于select的文件描述符集合,只不过select是使用fd_set来定义的,而poll的集合是一个数组。

struct pollfd的成员:

struct pollfd {
    int   fd;      :文件描述符
    short events;  :设置我们希望发生的事件,比如读事件,这个需要我们自己设置
    short revents; :实际发生的事件,比如读事件,由poll机制自己设置
};
View Code
struct pollfd fds[2];
fds[0].fd = 0;
fds[0].events = POLLIN;//读事件(输入事件)
                                
fds[1].fd = 3;
fds[1].events = POLLIN;//读事件(输入事件)
View Code

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 }
View Code

 

转载于:https://www.cnblogs.com/kelamoyujuzhen/p/9453368.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值