高级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("~~~~~~~~~~~~~\n");
12         ret=read(0,buf,sizeof(buf));
13         if(ret > 0) printf("%s\n",buf);
14         printf("~~~~~~~~~~~~~\n");
15     }
16     return 0;
17 }
View Code

read第一次调用时会等待stdin输入,没有输入的话会一直阻塞。取地输入后便继续向下执行,不会一直卡在read调用处

读鼠标阻塞

 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\n",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("~~~~~~~~~~~~~\n");
28         ret=read(mousefd,&cord,sizeof(cord));
29         if(ret > 0) printf("%d\n",cord);
30         printf("~~~~~~~~~~~~~\n");
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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",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\n",cord);
45         }
46     }
47     return 0;
48 }
View Code

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

主线程:读键盘

次线程:读鼠标

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

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <strings.h>
 5 #include <signal.h>
 6 #include <errno.h>
 7 #include <sys/types.h>
 8 #include <sys/stat.h>
 9 #include <fcntl.h>
10 #include <pthread.h>
11 
12 void print_err(char *str,int line,int err_no)
13 {
14     printf("%d,%s: %s\n",line,str,strerror(err_no));
15     exit(-1);
16 }
17 
18 void *pth_fun(void *pth_arg)
19 {    
20     int ret=0;
21     int cord=0;  //鼠标坐标
22     int mousefd = *(int *)pth_arg;
23     while(1)
24     {
25         ret=read(mousefd,&cord,sizeof(cord));
26         if(ret > 0) printf("%d\n",cord);
27     }
28 
29     return NULL;
30 }
31 
32 int main(void)
33 {
34     int ret = 0;
35     pthread_t tid;
36     char buf[100]={0};//读键盘
37     int mousefd = -1;
38     
39     mousefd=open("/dev/input/mouse0",O_RDONLY);
40     if(-1 == mousefd) print_err("open mouse0 fail",__LINE__,errno);    
41     
42     ret = pthread_create(&tid, NULL, pth_fun, (void *)&mousefd);   
43     if(0 != ret) print_err("pthread_create fail",__LINE__,ret);    
44     
45     while(1)
46     {
47         ret = 0;
48         ret=read(0,buf,sizeof(buf));
49         if(ret > 0) printf("%s\n",buf);
50     }  
51     
52     return 0;
53 }
View Code

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

代码演示

 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\n",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 cord=0;  //鼠标坐标
21     int mousefd = -1;
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     while(1)
29     {
30         ret=read(0,buf,sizeof(buf));
31         if(ret > 0) printf("%s\n",buf);
32         ret=0;
33         ret=read(mousefd,&cord,sizeof(cord));
34         if(ret > 0) printf("%d\n",cord);
35     }
36     return 0;
37 }
View Code

问题:把0设置为非阻塞,请问使用scanf从键盘读数据时阻塞的吗?

代码演示:

 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\n",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 cord=0;  //鼠标坐标
21     int mousefd = -1;
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     while(1)
29     {
30         ret=scanf("%s",buf);
31         if(ret > 0) printf("%s\n",buf);
32         printf("~~~~~~~~~~~~~~\n");
33     }
34     return 0;
35 }
View Code

不会阻塞。为什么?scanf()是调用read(0, ...)来实现的,scanf的阻塞是因为调用read阻塞读0描述符导致的,既然已经将0改为了非阻塞,read读0就不再阻塞,自然scanf也就不再阻塞。所以如果你不想scanf阻塞的话,就可以将0描述符设置为非阻塞。当然我这个话只能针对Linux,因为fcntl函数是Linux的系统函数,不是标准C库函数,这个函数在windows那边不一定支持,在windows等系统下,如何设置非阻塞,他们的系统API可能不一样。不过对于c的标准IO函数scanf来说,都是希望阻塞读的,基本不会遇到需要将scanf改为非阻塞的情况。

 

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值