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:读取普通文件

  1. 准备文件 test.txt
echo "Hello, Linux!" > test.txt
  1. 编写代码 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;
}
  1. 编译并运行
gcc read_success.c -o read_success
./read_success
  1. 运行结果
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​​ 是什么?”

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐