返回-1就是错?read函数的四副面孔,你真的懂了吗?
本文深入解析Linux C编程中read函数的四种返回值场景:1. 返回值>0表示成功读取相应字节数;2. 返回0表示到达文件末尾(EOF);3. 返回-1且errno非EAGAIN表示真实错误;4. 返回-1且errno为EAGAIN是非阻塞I/O下的正常状态。重点剖析了非阻塞模式下EAGAIN的特殊含义,强调正确区分错误与暂时无数据状态对编写健壮系统程序的重要性。文章通过四个具体案例演示
80T资源合集下载
链接:https://pan.quark.cn/s/5643428d4f9f
在 Linux C 编程的世界里,read 函数是我们与文件系统打交道的基石。几乎每个 C 程序员都背过它的“三板斧”:返回值大于 0 表示成功,等于 0 表示文件结束(EOF),小于 0 表示出错。
但是,当你的面试官追问:“read 返回 -1,就一定代表发生了不可挽回的错误吗?”
如果你稍有迟疑,那么这篇文章就是为你准备的。今天,我们将深入探索 read 函数返回值的四种核心场景,特别是那张最容易被误解的“面孔”——在非阻塞 I/O 下的 -1。
read 函数速览
我们先来回顾一下它的原型:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
-
fd: 文件描述符,由 open 函数返回。 -
buf: 用来存放读取数据的缓冲区。 -
count: 想要读取的最大字节数。 - 返回值
ssize_t: 一个有符号的整型,这是理解其返回值的关键。
现在,让我们通过四个独立的案例,揭开它返回值的四副“面孔”。
第一副面孔:成功读取 (返回值 > 0)
这是最常见、最理想的情况。read 函数返回一个正整数,代表本次成功从文件中读取到的字节数。
案例1:读取普通文件
- 准备文件
test.txt
echo "Hello, Linux!" > test.txt
- 编写代码
read_success.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
int main() {
int fd = open("test.txt", O_RDONLY);
if (fd < 0) {
perror("open");
exit(1);
}
char buffer[100];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
if (bytes_read > 0) {
printf("Success! Read %ld bytes.\n", bytes_read);
printf("Content: '%.*s'\n", (int)bytes_read, buffer);
}
close(fd);
return 0;
}
- 编译并运行
gcc read_success.c -o read_success
./read_success
- 运行结果
Success! Read 14 bytes.
Content: 'Hello, Linux!
'
分析:read 成功读取了文件中的 14 个字节(包括换行符),并返回 14。一切尽在掌握。
第二副面孔:文件末尾 (返回值 == 0)
当 read 返回 0 时,它并不是说“什么也没读到”,而是在传递一个明确的信号:文件已经读完,没有更多数据了 (End-Of-File, EOF)。
案例2:循环读取直到文件末尾 我们继续使用上面的 test.txt 文件。
// read_eof.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
int main() {
int fd = open("test.txt", O_RDONLY);
// ... 错误检查 ...
char buffer[5]; // 使用一个较小的缓冲区来演示多次读取
ssize_t bytes_read;
int read_count = 0;
while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) {
read_count++;
printf("Read #%d: %ld bytes\n", read_count, bytes_read);
}
printf("\nLoop finished. Last read returned: %ld\n", bytes_read);
if (bytes_read == 0) {
printf("Reached End-Of-File (EOF).\n");
}
close(fd);
return 0;
}
编译并运行
gcc read_eof.c -o read_eof
./read_eof
运行结果
Read #1: 5 bytes
Read #2: 5 bytes
Read #3: 4 bytes
Loop finished. Last read returned: 0
Reached End-Of-File (EOF).
分析:程序分 3 次读完了 14 字节的数据。当第四次调用 read 时,由于已经到达文件末尾,它返回了 0,循环终止。
第三副面孔:真正错误 (返回值 == -1, errno != EAGAIN)
当 read 返回 -1 时,表示发生了错误。此时,我们必须检查全局变量 errno 来找出错误的原因。
案例3:读取一个无效的文件描述符
// read_error.c
#include <stdio.h>
#include <unistd.h>
int main() {
char buffer[10];
int invalid_fd = -5; // 一个明显无效的 fd
ssize_t bytes_read = read(invalid_fd, buffer, sizeof(buffer));
if (bytes_read == -1) {
// 使用 perror 来打印与 errno 对应的错误信息
perror("read failed");
}
return 1;
}
编译并运行
gcc read_error.c -o read_error
./read_error
运行结果
read failed: Bad file descriptor
分析:read 返回 -1,perror 根据 errno 的值(此时为 EBADF)打印出了清晰的错误信息 "Bad file descriptor"。这是一个真正的、需要处理的错误。
第四副面孔:“请稍后再试” (返回值 == -1, errno == EAGAIN)
这是最精妙、也最关键的一种情况。它仅在对设备文件或网络文件进行非阻塞 (Non-blocking) 读取时出现。
当 read 返回 -1 且 errno 被设置为 EAGAIN (或 EWOULDBLOCK,在大多数系统上它们是同一个值) 时,它不是一个错误!它是在告诉你:“数据还没准备好,你现在读不到任何东西,但请稍后再来试试。”
重要前提:普通磁盘文件的读取不会发生这种情况,因为对于普通文件,数据总是在那里,读取操作总能立即完成(或者读到 EOF)。
案例4:非阻塞读取终端(设备文件)
// read_nonblock.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
int main() {
// 以非阻塞方式打开终端设备 /dev/tty
int fd = open("/dev/tty", O_RDONLY | O_NONBLOCK);
if (fd < 0) {
perror("open /dev/tty");
exit(1);
}
char buffer[10];
ssize_t bytes_read;
printf("Trying to read from terminal (non-blocking)...\n");
sleep(2); // 等待2秒,让你看到期间没有输入
bytes_read = read(fd, buffer, sizeof(buffer));
if (bytes_read == -1) {
if (errno == EAGAIN) {
printf("\nThis is not a real error!\n");
printf("errno is EAGAIN, which means: No data available right now. Try again later.\n");
} else {
perror("A real read error occurred");
}
} else {
printf("Successfully read %ld bytes.\n", bytes_read);
}
close(fd);
return 0;
}
编译并运行(不要输入任何东西)
gcc read_nonblock.c -o read_nonblock
./read_nonblock
运行结果
Trying to read from terminal (non-blocking)...
This is not a real error!
errno is EAGAIN, which means: No data available right now. Try again later.
分析:程序没有像阻塞模式那样卡住等待输入。read 立即返回 -1,但我们检查 errno 发现它是 EAGAIN。这正是非阻塞 I/O 的核心——立即返回,不等待。
总结:read 返回值的完整处理逻辑
现在,我们可以构建一个处理 read 返回值的完整逻辑框架:
ssize_t n = read(fd, buf, count);
if (n > 0) {
// 面孔1: 成功读取了 n 字节
// 处理数据...
} else if (n == 0) {
// 面孔2: 到达文件末尾
// 清理并关闭...
} else { // n == -1
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 面孔4: 非阻塞模式下,暂时无数据
// 等待一会儿再试,或者去做别的事情...
} else {
// 面孔3: 发生了真正的错误
// 记录日志,退出程序...
perror("read error");
}
}
结论 read 函数远比初看起来要深刻。理解它返回值的四副面孔,特别是 -1 的两种截然不同的含义,是编写健壮、高效的系统程序(尤其是网络服务器和设备驱动)的必备技能。下次再遇到 read 返回 -1 时,请务必多问一句:“errno 是什么?”

更多推荐
所有评论(0)