高级IO——非阻塞IO

Posted kelamoyujuzhen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高级IO——非阻塞IO相关的知识,希望对你有一定的参考价值。

读某些文件时,如果文件没有数据的话,往往会导致读操作会阻塞(休眠)。比如

①读鼠标、键盘等字符设备文件

读键盘阻塞

技术分享图片
 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<unistd.h>
 4 
 5 int main(void)
 6 {
 7     char buf[100]={0};
 8     int ret=0;
 9     while(1)
10     {
11         printf("~~~~~~~~~~~~~
");
12         ret=read(0,buf,sizeof(buf));
13         if(ret > 0) printf("%s
",buf);
14         printf("~~~~~~~~~~~~~
");
15     }
16     return 0;
17 }
View Code

读鼠标阻塞

技术分享图片
 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<unistd.h>
 4 #include<errno.h>
 5 #include<string.h>
 6 #include <sys/types.h>
 7 #include <sys/stat.h>
 8 #include <fcntl.h>
 9 
10 
11 void print_err(char *str,int line,int err_no)
12 {
13     printf("%d,%s: %s
",line,str,strerror(err_no));
14     exit(-1);
15 }
16 int main(void)
17 {
18     int cord=0;  //鼠标坐标
19     int ret=0;
20     int mousefd = -1;
21     
22     mousefd=open("/dev/input/mouse0",O_RDONLY);
23     if(-1 == mousefd) print_err("open mouse0 fail",__LINE_,errno);
24 
25     while(1) 
26     {
27         printf("~~~~~~~~~~~~~
");
28         ret=read(mousefd,&cord,sizeof(cord));
29         if(ret > 0) printf("%d
",cord);
30         printf("~~~~~~~~~~~~~
");
31     }
32     return 0;
33 }
View Code

read这个函数,不管是成功返回 还是 出错返回,只要返回就接着往下执行

②读管道文件(有名无名)

读普通文件会阻塞吗?

读普通文件时,如果读到了数据就成功返回,如果没有读到数据返回0(注意是返回0,不是出错返回),总之不会阻塞。

总之,不管有没有从普通文件读到数据,读普通文件时都不会阻塞。

写文件时会阻塞吗?

在写某些文件时,当文件不能立即接收写入的数据时,也可能会导致写操作阻塞,一直阻塞到写成功为止。一把来说,写文件不会阻塞,因此我们不考虑写文件阻塞的情况,我们这里只讲读文件阻塞的情况。

阻塞是好还是坏

实际上读文件因为没有数据而阻塞,其实是好事,因为这样子就进入休眠状态,休眠时就不会占用CPU,节省了cpu的资源。

我能不能将阻塞的读修改为非阻塞的读呢?

答:可以,非阻塞读的意思就是说,如果有数据就成功读到,如果没有读到数据就出错返回,而不是阻塞。

疑问:既然阻塞读很好,为什么提供非阻塞读呢?

答:尽管我们很少非阻塞的读,但是有些时候还真需要非阻塞的读,因此OS还是为我们提供了非阻塞操作方式。

如何实现非阻塞读

打开文件时指定O_NONBLOCK状态标志

以读鼠标为例

技术分享图片
 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<unistd.h>
 4 #include<errno.h>
 5 #include<string.h>
 6 #include <sys/types.h>
 7 #include <sys/stat.h>
 8 #include <fcntl.h>
 9 
10 
11 void print_err(char *str,int line,int err_no)
12 {
13     printf("%d,%s: %s
",line,str,strerror(err_no));
14     exit(-1);
15 }
16 int main(void)
17 {
18     int cord=0;  //鼠标坐标
19     int ret=0;
20     int mousefd = -1;
21     
22     mousefd=open("/dev/input/mouse0",O_RDONLY|O_NONBLOCK);
23     if(-1 == mousefd && errno!=EAGAIN) print_err("open mouse0 fail",__LINE_,errno);
24 
25     while(1) 
26     {
27         ret=read(mousefd,&cord,sizeof(cord));
28         if(ret > 0) printf("%d
",cord);
29     }
30     return 0;
31 }
View Code

当制定O_NONBLOCK读鼠标的时候,没有数据的话,ret回返回-1,并且errno被设置为EAGAIN

在讲IPC有名管道时,如果不希望阻塞的话,就可以在open打开“有名管道”时,指定O_NONBLOCK,然后读有名管道无数据时就不会阻塞。

通过fcntl函数指定O_NONBLOCK来实现

什么情况下会使用fcntl来实现,

情况1:当文件已经被open打开了,但是open是并没有指定你要的文件状态标志,而你又不下你给去修改open的参数,此时就是可以使用fcntl来重设或者补设。

情况2:没办法在open指定,你手里只有一个文件描述符fd,此时就使用fcntl来重设或者补设。比如无名管道,无名管道连名字都没有,没办法使用open函数,无名管道是使用pipe函数来返回文件描述符的,如果过你想非阻塞的读无名管道的话,是没有办法通过open来指定O_NONBLOCK的,此时就需要使用fcntl来重设或者补设。

当然我们使用fcntl不仅仅只能重设或者补设O_NONBLOCK,也可以重设或者补设O_TRUNC/O_APPEND等任何你需要的“文件状态”标志。

例子:将0设置为O_NONBLOCK。设置有两种方式:

重设

fcntl(0, F_SETFL, O_RDONLY|O_NONBLOCK);

代码演示

技术分享图片
 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<unistd.h>
 4 #include<errno.h>
 5 #include<string.h>
 6 #include <sys/types.h>
 7 #include <sys/stat.h>
 8 #include <fcntl.h>
 9 
10 void print_err(char *str,int line,int err_no)
11 {
12     printf("%d,%s: %s
",line,str,strerror(err_no));
13     exit(-1);
14 }
15 
16 int main(void)
17 {
18     char buf[100]={0};
19     int ret=0;
20     fcntl(0, F_SETFL, O_RDONLY|O_NONBLOCK);
21     while(1)
22     {
23         ret=read(0,buf,sizeof(buf));
24         if(ret > 0) printf("%s
",buf);
25     }
26     return 0;
27 }
View Code

补设

技术分享图片
1 flag = fcntl(0, F_GETFL);     //获取原有文件状态标志
2 flag = flag | O_NONBLOCK;     //通过|操作,在已有的标志上增设O_NONBLOCK
3 fcntl(0, F_SETFL, flag);         //将修改后的“文件状态标志”设置回去
View Code

 代码演示

技术分享图片
 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<unistd.h>
 4 #include<errno.h>
 5 #include<string.h>
 6 #include <sys/types.h>
 7 #include <sys/stat.h>
 8 #include <fcntl.h>
 9 
10 void print_err(char *str,int line,int err_no)
11 {
12     printf("%d,%s: %s
",line,str,strerror(err_no));
13     exit(-1);
14 }
15 
16 int main(void)
17 {
18     char buf[100]={0};
19     int ret=0;
20     int flag=0;
21     flag = fcntl(0, F_GETFL);     
22     flag = flag | O_NONBLOCK;     
23     fcntl(0, F_SETFL, flag);     
24     while(1)
25     {
26         ret=read(0,buf,sizeof(buf));
27         if(ret > 0) printf("%s
",buf);
28     }
29     return 0;
30 }
View Code

实现同时“读鼠标”和“读键盘”

由于一般情况下,“读鼠标”和“读键盘”都是阻塞的,为了不要让“读鼠标”和“读键盘”因为阻塞而相互干扰,可以采取如下办法来读。

①fork子进程,然后父子进程两线任务

父进程:读键盘

子进程:读鼠标

这样可以,但是并不主张这么做。多线任务使用进程来做开销太大

代码演示

技术分享图片
 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<unistd.h>
 4 #include<errno.h>
 5 #include<string.h>
 6 #include <sys/types.h>
 7 #include <sys/stat.h>
 8 #include <fcntl.h>
 9 
10 void print_err(char *str,int line,int err_no)
11 {
12     printf("%d,%s: %s
",line,str,strerror(err_no));
13     exit(-1);
14 }
15 
16 int main(void)
17 {
18     int cord=0;  //鼠标坐标
19     int mousefd = -1;
20     char buf[100]={0};//读键盘
21     int ret=0;    
22     
23     mousefd=open("/dev/input/mouse0",O_RDONLY);
24     if(-1 == mousefd) print_err("open mouse0 fail",__LINE__,errno);
25     
26     fcntl(0, F_SETFL, O_RDONLY|O_NONBLOCK);    
27     fcntl(mousefd, F_SETFL, O_RDONLY|O_NONBLOCK);    
28     
29     ret = fork();
30     if(ret > 0)//父进程
31     {
32         while(1)
33         {
34             ret = 0;
35             ret=read(0,buf,sizeof(buf));
36             if(ret > 0) printf("%s
",buf);
37         }
38     }
39     else if(ret == 0)//子进程
40     {
41         while(1)
42         {
43             ret=read(mousefd,&cord,sizeof(cord));
44             if(ret > 0) printf("%d
",cord);
45         }
46     }
47     return 0;
48 }
View Code

②创建次线程,主线程和次线程两线任务

主线程:读键盘

次线程:读鼠标

这才是我们经常实现的方式。代码演示:

 

③将鼠标和键盘设置为“非阻塞”,while轮询的读。

 

以上是关于高级IO——非阻塞IO的主要内容,如果未能解决你的问题,请参考以下文章

高级IO,阻塞于非阻塞

高级 IO(非阻塞多路复用异步存储映射文件锁)

高级IO模型

高级IO模型

高级IO模型

高级IO模型