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

两个C程序管道双向通信问题:接收返回值始终为0的排查

问题:父子进程双向管道通信异常,父进程读取返回值为0

需求是编写两个C程序ex1.c和ex2.c:

  • ex1通过fork创建子进程并执行ex2
  • ex1向ex2发送2个字符串和1个整数
  • ex2向ex1返回一个整数

编译运行./ex1后,发现ex1读取到的返回整数始终为0,以下是原代码及解决方案:

原代码

ex1.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    int pipefd[2];
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    int integerData = 42;
    char string1[] = "Hello good";
    char string2[] = "World ";

    pid_t pid = fork();

    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // Child process (loopcmp.c)
        close(pipefd[1]);  // Close the write end of the pipe

        // Redirect stdin to read from the pipe
        if (dup2(pipefd[0], STDIN_FILENO) == -1) {
            perror("dup2");
            exit(EXIT_FAILURE);
        }

        // Execute ex1.c
        execlp("./ex1", "ex1", NULL);

        // If execlp returns, an error occurred
        perror("execlp");
        exit(EXIT_FAILURE);
    } else {
        // Parent process (nicecmp.c)
        close(pipefd[0]);  // Close the read end of the pipe

        // Serialize the data as a single string
        char buffer[256];
        snprintf(buffer, sizeof(buffer), "%s:%s:%d", string1, string2, integerData);

        // Write the serialized string to the pipe
        if (write(pipefd[1], buffer, strlen(buffer)) == -1) {
            perror("write");
            exit(EXIT_FAILURE);
        }

        close(pipefd[1]);  // Close the write end of the pipe

        // Wait for the child process to finish
        int status;
        wait(&status);
        int num;
        read(pipefd[0], &num, sizeof(int));
        close(pipefd[0]);
        printf("the num from loopcmp: %d\n",num);
    }

    return 0;
}

ex2.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    char buffer[256];
    // Read the serialized string from stdin (the pipe)
    if (read(STDIN_FILENO, buffer, sizeof(buffer)) == -1) {
        perror("read");
        exit(EXIT_FAILURE);
    }

    // Parse the data from the serialized string
    char string1[100];
    char string2[100];
    int integerData;
    sscanf(buffer, "%[^:]:%[^:]:%d", string1, string2, &integerData);

    // Now you have the two strings and the integer data from nicecmp.c
    
    printf("Received data from nicecmp.c:\n");
    printf("String 1: %s\n", string1);
    printf("String 2: %s\n", string2);
    printf("Integer: %d\n", integerData);
    int num = 556;
    write(STDOUT_FILENO, &num, sizeof(int));

    return 0;
}

问题分析

  1. 子进程执行目标错误:原代码中子进程执行的是./ex1而非./ex2,导致逻辑完全混乱
  2. 单管道无法实现双向通信:管道是半双工的,单个管道只能单向传输数据;且父进程提前关闭了管道读端,后续尝试读取已关闭的描述符必然失败
  3. 子进程输出未定向到父进程可读管道:ex2向STDOUT_FILENO写入的数据没有被引导到ex1的读取管道,父进程无法获取返回值
  4. 读取时机错误:父进程在wait子进程退出后才尝试读取,此时子进程的输出资源已被释放,无法读取有效数据

修正后的代码

ex1.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    int pipe_parent_to_child[2]; // 父进程→子进程的通信管道
    int pipe_child_to_parent[2]; // 子进程→父进程的通信管道

    // 创建两个双向管道
    if (pipe(pipe_parent_to_child) == -1 || pipe(pipe_child_to_parent) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    int integerData = 42;
    char string1[] = "Hello good";
    char string2[] = "World ";

    pid_t pid = fork();

    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 子进程:执行ex2
        close(pipe_parent_to_child[1]); // 关闭父→子管道的写端
        close(pipe_child_to_parent[0]); // 关闭子→父管道的读端

        // 重定向stdin到父→子管道的读端
        if (dup2(pipe_parent_to_child[0], STDIN_FILENO) == -1) {
            perror("dup2 stdin");
            exit(EXIT_FAILURE);
        }
        // 重定向stdout到子→父管道的写端
        if (dup2(pipe_child_to_parent[1], STDOUT_FILENO) == -1) {
            perror("dup2 stdout");
            exit(EXIT_FAILURE);
        }

        // 关闭已重定向的原始管道描述符
        close(pipe_parent_to_child[0]);
        close(pipe_child_to_parent[1]);

        // 执行目标程序ex2
        execlp("./ex2", "ex2", NULL);

        // execlp返回则说明执行失败
        perror("execlp");
        exit(EXIT_FAILURE);
    } else {
        // 父进程
        close(pipe_parent_to_child[0]); // 关闭父→子管道的读端
        close(pipe_child_to_parent[1]); // 关闭子→父管道的写端

        // 序列化数据为带终止符的字符串
        char buffer[256];
        snprintf(buffer, sizeof(buffer), "%s:%s:%d", string1, string2, integerData);

        // 写入数据到子进程,包含字符串终止符确保解析正确
        ssize_t bytes_written = write(pipe_parent_to_child[1], buffer, strlen(buffer) + 1);
        if (bytes_written == -1) {
            perror("write to child");
            exit(EXIT_FAILURE);
        }

        // 关闭写端,告知子进程数据发送完毕
        close(pipe_parent_to_child[1]);

        // 读取子进程返回的整数
        int num;
        ssize_t bytes_read = read(pipe_child_to_parent[0], &num, sizeof(int));
        if (bytes_read == -1) {
            perror("read from child");
            exit(EXIT_FAILURE);
        } else if (bytes_read != sizeof(int)) {
            fprintf(stderr, "读取子进程数据不完整\n");
            exit(EXIT_FAILURE);
        }

        // 关闭读端
        close(pipe_child_to_parent[0]);

        // 等待子进程退出并检查状态
        int status;
        wait(&status);
        if (WIFEXITED(status)) {
            printf("子进程正常退出,退出码:%d\n", WEXITSTATUS(status));
        }

        printf("从ex2收到的整数:%d\n", num);
    }

    return 0;
}

ex2.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    char buffer[256];
    // 从stdin读取父进程发送的数据
    ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
    if (bytes_read == -1) {
        perror("read from parent");
        exit(EXIT_FAILURE);
    }
    // 手动添加字符串终止符,确保sscanf解析正确
    buffer[bytes_read] = '\0';

    // 解析字符串中的数据
    char string1[100];
    char string2[100];
    int integerData;
    if (sscanf(buffer, "%[^:]:%[^:]:%d", string1, string2, &integerData) != 3) {
        fprintf(stderr, "解析父进程数据失败\n");
        exit(EXIT_FAILURE);
    }

    printf("收到来自ex1的数据:\n");
    printf("字符串1:%s\n", string1);
    printf("字符串2:%s\n", string2);
    printf("整数:%d\n", integerData);

    // 向父进程返回整数
    int num = 556;
    ssize_t bytes_written = write(STDOUT_FILENO, &num, sizeof(int));
    if (bytes_written == -1) {
        perror("write to parent");
        exit(EXIT_FAILURE);
    } else if (bytes_written != sizeof(int)) {
        fprintf(stderr, "向父进程写入数据不完整\n");
        exit(EXIT_FAILURE);
    }

    return 0;
}

关键修正点

  • 新增双向管道:用两个独立管道分别处理父到子、子到父的通信,解决单管道半双工的限制
  • 修正子进程执行目标:将execlp的执行路径改为./ex2,确保启动正确的程序
  • 正确重定向IO:子进程将stdin绑定到父→子管道读端,stdout绑定到子→父管道写端,同时关闭冗余的管道描述符
  • 完善数据传输完整性:写入字符串时包含终止符,读取后手动补充终止符;检查读写字节数,避免不完整传输
  • 调整读取时机:父进程先读取子进程返回数据,再等待子进程退出(顺序不影响,但需确保管道资源未被释放)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 05:30:54