高级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 }
读鼠标阻塞
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 }
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 }
当制定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 }
补设
1 flag = fcntl(0, F_GETFL); //获取原有文件状态标志 2 flag = flag | O_NONBLOCK; //通过|操作,在已有的标志上增设O_NONBLOCK 3 fcntl(0, F_SETFL, flag); //将修改后的“文件状态标志”设置回去
代码演示
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 }
实现同时“读鼠标”和“读键盘”
由于一般情况下,“读鼠标”和“读键盘”都是阻塞的,为了不要让“读鼠标”和“读键盘”因为阻塞而相互干扰,可以采取如下办法来读。
①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 }
②创建次线程,主线程和次线程两线任务
主线程:读键盘
次线程:读鼠标
这才是我们经常实现的方式。代码演示:
③将鼠标和键盘设置为“非阻塞”,while轮询的读。
以上是关于高级IO——非阻塞IO的主要内容,如果未能解决你的问题,请参考以下文章