You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言读取已满命名管道:确定再次写入前需读取的字节数

命名管道(FIFO)容量检测及写入恢复问题排查

问题背景

作业要求:

  • 检查命名管道(FIFO)的容量;
  • 确定从已满的管道中需要读取多少字节后才能再次写入。

当前代码可成功写入数据直至管道满,显示其容量为65536字节,但后续读取操作失败(返回值为-1,错误码为Resource temporarily unavailable),且出现未读取任何数据就成功写入的情况,输出Write successful after reading 0 bytes。

问题代码

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

int main() {
    int l = 0;
    int stream;
    char* fifo = "/tmp/fifo";
    mkfifo(fifo, 0666);
    char test = 255;
    int p = fork();
    char readtest;

    if (p > 0) { // 父进程
        wait(NULL);
        stream = open(fifo, O_RDWR | O_NONBLOCK);
        while (1) {
            if (read(stream, &readtest, 1) == 1) {
                l++;
                printf("Read byte %d, attempting write\n", l);
            } else {
                printf("Read failed, errno: %d (%s)\n", errno, strerror(errno));
            }
            if (write(stream, &test, 1) == 1) {
                printf("Write successful after reading %d bytes\n", l);
                return 0;
            }
        }
    } else if (p == 0) { // 子进程
        stream = open(fifo, O_RDWR | O_NONBLOCK);
        while (write(stream, &test, 1) == 1 ) {
            l++;
        }
        close(stream);
        printf("Wrote %d bytes, pipe full\n", l);
        _Exit(0);
    }
    return 0;
}

问题分析

  1. 管道数据被清空:父进程调用wait(NULL)等待子进程退出,子进程写完后关闭了FIFO的所有读写端(因以O_RDWR打开)。此时无任何进程持有管道的端,内核会自动清空缓冲区数据。父进程后续打开FIFO时管道为空,写入操作直接成功,导致Write successful after reading 0 bytes的异常输出。
  2. 非阻塞读取失败:父进程以O_NONBLOCK模式打开空管道,调用read时因无数据可读且是非阻塞模式,返回-1并设置errno为EAGAIN(对应错误信息Resource temporarily unavailable)。

解决方案

要保留管道数据并正确测试读取后写入的条件,需保证管道始终有至少一个端处于打开状态,避免数据被清空。调整后的代码如下:

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

int main() {
    int l = 0;
    int parent_read_fd, child_write_fd, parent_write_fd;
    char* fifo = "/tmp/fifo";
    unlink(fifo); // 清理已有FIFO,避免创建失败
    mkfifo(fifo, 0666);

    char test = 255;
    int p = fork();
    char readtest;

    if (p > 0) { // 父进程
        // 先打开读端,保持管道读端存在
        parent_read_fd = open(fifo, O_RDONLY);
        wait(NULL); // 等待子进程写满管道

        // 打开写端用于测试写入
        parent_write_fd = open(fifo, O_WRONLY | O_NONBLOCK);
        while (1) {
            ssize_t read_ret = read(parent_read_fd, &readtest, 1);
            if (read_ret == 1) {
                l++;
                printf("Read byte %d, attempting write\n", l);
            } else if (read_ret == 0) {
                printf("Pipe EOF reached\n");
                break;
            } else {
                if (errno != EAGAIN) {
                    printf("Read error: %s\n", strerror(errno));
                    break;
                }
            }

            // 尝试写入
            ssize_t write_ret = write(parent_write_fd, &test, 1);
            if (write_ret == 1) {
                printf("Write successful after reading %d bytes\n", l);
                break;
            } else if (errno != EAGAIN) {
                printf("Write error: %s\n", strerror(errno));
                break;
            }
        }

        close(parent_read_fd);
        close(parent_write_fd);
        unlink(fifo); // 清理FIFO
    } else if (p == 0) { // 子进程
        child_write_fd = open(fifo, O_WRONLY);
        while (write(child_write_fd, &test, 1) == 1 ) {
            l++;
        }
        printf("Wrote %d bytes, pipe full\n", l);
        sleep(2); // 等待父进程完成读取测试
        close(child_write_fd);
        _Exit(0);
    }
    return 0;
}

调整说明

  1. 保持管道端打开:父进程提前打开读端,子进程写完后不立即关闭写端,确保管道数据不会被内核清空。
  2. 分离读写端:父进程分别打开读端和写端,避免O_RDWR模式导致的自我连接问题,更符合FIFO的典型使用场景。
  3. 完善错误处理:添加对EAGAIN错误的判断,区分非阻塞模式下的正常状态与真正的读写错误。
  4. 清理残留文件:运行前后清理FIFO文件,避免因残留文件导致的异常。

内容的提问来源于stack exchange,提问作者crepper4454

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 23:44:57