一、fcntl函数的作用
read函数是典型的阻塞模型,当缓冲区里的数据不就绪的时候,会一直阻塞等待。这是正常的,因为文件描述符默认是阻塞IO,而我们可以通过 fcntl 接口函数将文件描述符设置为非阻塞IO。
设置成非阻塞IO以后,read函数会一直检测数据是否就绪,如果就绪就读取,并返回读取到的字符数;如果不就绪,就返回一个错误码。
二、fcntl 函数的声明
fcntl 函数的作用是操作一个文件的文件描述符,而设置成非阻塞IO只是 fcntl 函数的功能之一。
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
1、参数解析
第一个参数fd,指明你要操作哪个文件的文件描述符。
第二个参数cmd,也就是你要对该文件描述符进行何种操作。cmd的取值不同,后面追加的参数也不同。
cmd的取值 命令解析
F_DUPFD 复制一个现有的文件描述符
F_GETFD 或 F_SETFD 获得/设置文件描述符标记
F_GETFL 或 F_SETFL 获得/设置文件状态标记
F_GETOWN 或 F_SETOWN 获得/设置异步IO所有权
F_GETLK 或 F_SETLK( F_SETLKW ) 获得/设置记录锁
2、返回值解析
执行成功时,不同的cmd可能会对应不同的返回值,没有列举在下面的,比如F_SETFL,可能返回值类型为void。
执行出错时,也会有不同的返回值,此时可能就与cmd无关了。
三、使用fcntl 将文件描述符设置为非阻塞
1、设置非阻塞模式实现
以设置文件描述符为 0 的文件为例,将文件描述符设置为非阻塞状态,需要用到上述表格的第三个功能,获得/设置文件状态标记。基本思路为:第一步,先获取到原有的文件状态;第二步,在原有的基础上追加一种非阻塞状态。
函数准备好以后,在调用read函数之前先将 第0号文件描述符设置成非阻塞,然后再调用read函数。
非阻塞模式下,read函数如果发现数据尚未就绪,系统是以出错的形式返回的,很显然数据未就绪不算错误,那么要如何区分真正的错误 和 数据未就绪时的出错呢?
答案是errno,errno是错误码,正常情况下是0。当数据没有就绪的时候,errno的值是11,即EAGAIN 或者 EWOULDBLOCK,我们可以以此判断是否真的出错了。如果你在成功时打印errno,你会发现errno仍然是11,因为read读取到数据的时候,不会设置errno。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>void setNonBlock(){int fl = fcntl(0,F_GETFL); //获取文件描述符为0的文件状态if (fl < 0){perror("fcntl");return;}fcntl(0, F_SETFL, fl | O_NONBLOCK); //追加文件描述符的状态为非阻塞
}int main(){setNonBlock(); //设置为非阻塞模式while (1){char buffer[1024];ssize_t s = read(0,buffer,sizeof(buffer)-1); //非阻塞读取if (s > 0){buffer[s] = 0;write(1,buffer,s); //将读取到的内容打印到屏幕上printf("read success, res: %d, content: %s\n",s, buffer);}else{if(errno == EAGAIN || errno == EWOULDBLOCK){printf("read failed, res: %d , errno: %d\n", s , errno);}}sleep(1);}return 0;
}