read函数是典型的阻塞模型,当缓冲区里的数据不就绪的时候,会一直阻塞等待。这是正常的,因为文件描述符默认是阻塞IO,而我们可以通过 fcntl 接口函数将文件描述符设置为非阻塞IO。
设置成非阻塞IO以后,read函数会一直检测数据是否就绪,如果就绪就读取,并返回读取到的字符数;如果不就绪,就返回一个错误码。
fcntl 函数的作用是操作一个文件的文件描述符,而设置成非阻塞IO只是 fcntl 函数的功能之一。
函数原型:
#include
#include
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd ,struct flock* lock);
int fcntl(int fd, int cmd, ... /* arg */ );
第一个参数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 ) | 获得/设置记录锁 |
第三个参数arg,可有可无,由第二个参数决定,比如F_GETFL时候没有,F_SETFL时候有值
执行成功时,不同的cmd可能会对应不同的返回值,没有列举在下面的,比如F_SETFL,可能返回值类型为void。
成功则返回0,若有错误则返回-1,错误原因存于errno
以设置文件描述符为 fd 的文件为例,将文件描述符设置为非阻塞状态,需要用到上述表格的第三个功能,获得/设置文件状态标记。
基本思路为:第一步,先获取到原有的文件状态;第二步,在原有的基础上追加一种非阻塞状态。
void setNonBlock(){
int fl = fcntl(fd,F_GETFL); //获取文件描述符为fd的文件状态
if (fl < 0)
{
perror("fcntl");
return;
}
fcntl(fd, F_SETFL, fl | O_NONBLOCK); //追加文件描述符的状态为非阻塞
}
函数准备好以后,在调用read函数之前先将 第fd文件描述符设置成非阻塞,然后再调用read函数。
int main(){
setNonBlock(); //设置为非阻塞模式
while (1)
{
char buffer[1024];
ssize_t s = read(fd,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(2);
}
return 0;
}
测试结果如下,非阻塞模式下,read函数如果发现数据尚未就绪,系统是以出错的形式返回的,很显然数据未就绪不算错误,那么要如何区分真正的错误 和 数据未就绪时的出错呢?
答案是errno,errno是错误码,正常情况下是0。当数据没有就绪的时候,errno的值是11,即EAGAIN 或者 EWOULDBLOCK,我们可以以此判断是否真的出错了。如果你在成功时打印errno,你会发现errno仍然是11,因为read读取到数据的时候,不会设置errno。
read函数里有对EAGAIN的解释,EAGAIN会出现在当某个文件描述符被设置成非阻塞的时候。