Linux系统编程(3)文件读写操作
-
Linux系统编程(4)文件读写操作
通过read()读文件
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);每个参数分别是什么
参数 1:
int fd-
fd是文件描述符,一般来自:-
open()打开文件 -
或者标准输入
0(STDIN_FILENO)
-
-
内核用
fd找到你打开的“文件对象 + 当前偏移量”等状态。
参数 2:
void *buf-
你提供的缓冲区地址,用来装读出来的数据。
-
必须保证这块内存可写并且至少有
count个字节容量。 -
例如:
char buf[4096]; read(fd,buf,sizeof(buf));参数 3:
size_t count-
你希望这次最多读取多少字节。
-
注意:这是“最多”,不是“必须读满”。
每次调用read()函数,会从fd指向的文件的当前偏移开始读取len字节到buf所指向的内存中。执行成功时,返回写入buf中的字节数;出错时,返回-1,并设置errno值。fd的文件位置指针会向前移动,移动的长度由读取到的字节数决定。如果fd所指向的对象不支持seek操作(比如字符设备文件),则读操作总是从“当前”位置开始。
基本用法很简单。下面这个例子就是从文件描述符fd所指向的文件中读取数据并保存到word中。读取的字节数即unsigned long类型的大小,在Linux的32位系统上是4字节,在64位系统上是8字节。成功时,返回读取的字节数;出错时,返回-1:**usigned long world; ssize_t nr nr = read(fd,&world,sizeof(world)); if(nr==-1)......返回值三种情况(必须背熟)
情况 A:返回值
> 0-
表示实际读取到的字节数,比如返回 1000,就说明
buf[0..999]有效。 -
不保证等于
count:读文件也可能因为系统缓冲、信号等原因“读不满”。
情况 B:返回值
== 0-
表示 EOF(文件结束):已经读到文件末尾,再读就没有数据了。
-
对普通文件,这是“读完”的标志。
情况 C:返回值
== -1-
表示出错,具体原因在全局变量
errno里。 -
最常见的你需要认识两个:
-
errno == EINTR:被信号打断,通常重试即可 -
errno == EAGAIN / EWOULDBLOCK:非阻塞读时没数据(普通文件很少遇到)
-
读入所有字节
诚如前面所描述的,由于调用read()会有很多不同情况,如果希望处理所有错误并且真正每次读入len个字节(至少读到EOF),那么之前简单“粗暴”的read()调用并不合理。要实现这一点,需要有个循环和一些条件语句,如下:
ssize_t ret;//声明返回值 size_t length;//声明需要读取的字节长度 while(len>0 && ret=read(fd,buf,length) ){ if(ret==-1){ if(error==EINTR) continue; perror("read"); break; } len-=(size_t)ret; buf+=ret; }read()读出来的是原始字节,它只返回“实际读了多少字节”,不会自动在末尾补'\0'。所以:
- 如果把
read的结果当作 C 字符串来用,必须自己加终止符,并且要预留一个字节空间。
实例:
char buf[1024]; ssize_t n = read(fd, buf, sizeof(buf) - 1); // 预留 1 字节给 '\0' if (n > 0) { buf[n] = '\0'; // 手动补 '\0' printf("%s", buf); }补充两点常见坑:
-
read读到的内容里可能本来就包含'\0'(比如二进制文件),这时即使你手动补了终止符,printf("%s")也会在中间的'\0'提前截断。 -
如果
n == sizeof(buf)-1,上面这种写法仍然安全,因为你预留了 1 字节。
阻塞IO(先了解即可)
有时,开发人员不希望read()调用在没有数据可读时阻塞在那里。相反地,他们希望调用立即返回,表示没有数据可读。这种方式称为非阻塞I/O,它支持应用以非阻塞模式执行I/O操作,因而如果是读取多个文件,以防错过其他文件中的可用数据。
因此,需要额外检查errno值是否为EAGAIN。正如前面所讨论的,如果文件描述符以非阻塞模式打开(即open()调用中指定参数为O_NONBLOCK,open()调用的参数”),并且没有数据可读,read()调用会返回-1,并设置errno值为EAGAIN,而不是阻塞模式。当以非阻塞模式读文件时,必须检查EAGAIN,否则可能因为丢失数据导致严重错误。你可能会用到如下代码:char buf[MAX_SIZE]; usigned int len; start: ssize_t ret = read(fd,buf,len); if (ret==-1){ if(error==ENTER) goto satrt; else if(error==EAGAIN)....; else...; }怎么打开非阻塞设置
方法 A:
open时带O_NONBLOCKint fd = open("a.txt",O_RDONLY | O_NONBLOCK);方法 B:对已有 fd 用
fcntl打开非阻塞#include <fcntl.h> int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK);非阻塞读的正确姿势:配合 select/poll/epoll
非阻塞不是让你疯狂
while(read...)空转(那会 CPU 100%),而是:1)先等“可读事件”(有数据了再读)
2)再read(),并且通常要一直读到 EAGAIN 才停(把缓冲区读空)#include <unistd.h> #include <fcntl.h> #include <errno.h> #include <stdio.h> #include <poll.h> int main() { int fd = STDIN_FILENO; int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); char buf[1024]; while (1) { struct pollfd pfd = { .fd = fd, .events = POLLIN }; int r = poll(&pfd, 1, 5000); // 等 5 秒 if (r == 0) { puts("timeout..."); continue; } if (r < 0) { perror("poll"); break; } // 可读了:把当前能读的都读出来(直到 EAGAIN) while (1) { ssize_t n = read(fd, buf, sizeof(buf)); if (n > 0) { write(STDOUT_FILENO, buf, (size_t)n); continue; } if (n == 0) return 0; // EOF if (errno == EINTR) continue; if (errno == EAGAIN || errno == EWOULDBLOCK) break; perror("read"); return 1; } } return 0; } -