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

C语言三进程双管道通信问题:子进程W无输出且未进入第二个fork

问题描述

我正在编写一个使用管道的C语言程序,包含三个进程:父进程P、子进程R、子进程W。P与R通过第一个管道通信,P与W通过第二个管道通信。子进程R读取输入文件,将每行内容通过第一个管道发送给父进程P;父进程P读取内容并判断是否为回文(palindroma),若是则将其发送给子进程W。

代码实现

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

#define DIMBUF 1024

int palindroma(const char *stringa)
{
    int len = strlen(stringa);

    int j = 0;
    for (int i = len - 1; i >= 0; i--)
    {
        if (stringa[i] == stringa[j])
        {
            j++;
        }
        else
            return 0;
    }
    return 1;
}

void child_r(int piper, const char *filename, char tmp[DIMBUF])
{
    FILE *w_stream, *file;

    if ((w_stream = fdopen(piper, "w")) == NULL)
    {
        perror("writer piper_rp");
        exit(1);
    }

    printf("Sono il figlio R\n");

    if ((file = fopen(filename, "r")) == NULL)
    {
        perror("apertura file");
        exit(1);
    }

    while (fgets(tmp, DIMBUF, file) != NULL)
    {
        printf("\tLeggo dal file: %s", tmp);
        fputs(tmp, w_stream);
    }
    fclose(file);
    fclose(w_stream);
}

void child_w(int piper, char tmp[DIMBUF])
{
    FILE *r_stream;

    if ((r_stream = fdopen(piper, "r")) == NULL)
    {
        perror("reader");
        exit(1);
    }

    while (fgets(tmp, DIMBUF, r_stream) != NULL)
    {
        printf("\tMessaggio ricevuto: %s\n", tmp);
    }

    fclose(r_stream);
    exit(0);
}

void father(int piper_rp, char tmp[DIMBUF], int piper_pw)
{
    FILE *r_stream, *w_stream;
    int len;

    if ((r_stream = fdopen(piper_rp, "r")) == NULL)
    {
        perror("reader");
        exit(1);
    }

    if ((w_stream = fdopen(piper_pw, "w")) == NULL)
    {
        perror("writer piper_pw");
        exit(1);
    }

    while (fgets(tmp, DIMBUF, r_stream) != NULL)
    {
        len = strlen(tmp);
        if (tmp[len - 1] == '\n')
            tmp[len - 1] = 0;

        int pal = palindroma(tmp);

        if (pal)
        {
            printf("\tMessaggio inviato: %s\n", tmp);
            fputs(tmp, w_stream);
        }
    }

    fclose(w_stream);
    fclose(r_stream);
}

int main(int argc, char const *argv[])
{
    int piper_rp[2];
    int piper_pw[2];
    char tmp[DIMBUF];
    const char *filename = argv[1];

    if (argc != 2)
    {
        fprintf(stderr, "Utilizzo %s file.txt \n ", argv[0]);
        exit(1);
    }

    if (pipe(piper_pw) == -1)
    {
        perror("errore pipe_pw");
        exit(1);
    }

    if (pipe(piper_rp) == -1)
    {
        perror("errore pipe_rp");
        exit(1);
    }

    if (fork() == 0)
    {
        close(piper_rp[0]);
        child_r(piper_rp[1], filename, tmp);
    }
    else
    {
        // padre
        sleep(1);
        close(piper_rp[1]);
        close(piper_pw[0]);
        printf("\nSono il padre P\n");
        father(piper_rp[0], tmp, piper_pw[1]);

        if (fork() == 0)
        {
            printf("\nSono il figlio W\n");
            close(piper_pw[1]);
            child_w(piper_pw[0], tmp);
        }
        else
            wait(NULL);
    }

    return 0;
}

运行输出

Sono il figlio R
	Leggo dal file: Ciao
	Leggo dal file: Mondo
	Leggo dal file: Ale
	Leggo dal file: emme
	Leggo dal file: piip
	Leggo dal file: veffev
	Leggo dal file: Scatola
	Leggo dal file: computer
	Leggo dal file: america
	Leggo dal file: abba
	Leggo dal file: acca
	Leggo dal file: cielo
	Leggo dal file: lampada
	Leggo dal file: effe
Sono il padre P
	Messaggio inviato: emme
	Messaggio inviato: piip
	Messaggio inviato: veffev
	Messaggio inviato: abba
	Messaggio inviato: acca
	Messaggio inviato: effe

疑问

为何子进程W没有任何输出?为何程序从未进入第二个fork?


问题分析与修复

核心错误点

  1. 父进程提前关闭W管道的读端,导致子进程W无法读取
    父进程在fork子进程W前,已经调用close(piper_pw[0]),子进程W继承的该文件描述符已失效,fdopen会失败,无法读取管道内容。

  2. 父进程先完成所有写入操作再fork子进程W,且写入内容无换行符
    父进程执行完father函数后才创建子进程W,此时管道写端已关闭;同时父进程写入管道的回文字符串被去掉了换行符,子进程W用fgets读取时会一直等待换行符或EOF,导致阻塞。

  3. 进程未正确关闭无关管道端
    子进程R未关闭W管道的两端,父进程的管道引用计数异常,可能导致读取进程无法感知EOF。

修复步骤

1. 调整fork子进程W的时机,保留有效管道端

父进程应先创建子进程W,再处理R管道的数据,避免提前关闭W管道的读端。修改main函数的父进程分支:

else
{
    close(piper_rp[1]); // 父进程关闭R管道写端

    // 先fork子进程W
    if (fork() == 0)
    {
        close(piper_pw[1]); // 子进程W关闭写端
        close(piper_rp[0]); // 子进程W关闭无关的R管道读端
        printf("\nSono il figlio W\n");
        child_w(piper_pw[0], tmp);
    }
    else
    {
        close(piper_pw[0]); // 父进程关闭W管道读端
        sleep(1);
        printf("\nSono il padre P\n");
        father(piper_rp[0], tmp, piper_pw[1]);
        fflush(stdout);
        close(piper_pw[1]); // 父进程写完后关闭写端,触发子进程W的EOF
        wait(NULL);
    }
}

2. 给写入管道的内容添加换行符

修改father函数的写入逻辑,确保子进程W的fgets能正常读取完整行:

if (pal)
{
    printf("\tMessaggio inviato: %s\n", tmp);
    fputs(tmp, w_stream);
    fputs("\n", w_stream); // 添加换行符
}

3. 让子进程R关闭无关管道端

修改子进程R的fork分支,关闭不需要的W管道两端:

if (fork() == 0)
{
    close(piper_rp[0]);
    close(piper_pw[0]); // 关闭W管道读端
    close(piper_pw[1]); // 关闭W管道写端
    child_r(piper_rp[1], filename, tmp);
}

修复后运行效果

子进程W会正常输出所有回文字符串:

Sono il figlio R
	Leggo dal file: Ciao
	Leggo dal file: Mondo
	Leggo dal file: Ale
	Leggo dal file: emme
	Leggo dal file: piip
	Leggo dal file: veffev
	Leggo dal file: Scatola
	Leggo dal file: computer
	Leggo dal file: america
	Leggo dal file: abba
	Leggo dal file: acca
	Leggo dal file: cielo
	Leggo dal file: lampada
	Leggo dal file: effe

Sono il padre P
	Messaggio inviato: emme
	Messaggio inviato: piip
	Messaggio inviato: veffev
	Messaggio inviato: abba
	Messaggio inviato: acca
	Messaggio inviato: effe

Sono il figlio W
	Messaggio ricevuto: emme
	Messaggio ricevuto: piip
	Messaggio ricevuto: veffev
	Messaggio ricevuto: abba
	Messaggio ricevuto: acca
	Messaggio ricevuto: effe

内容的提问来源于stack exchange,提问作者Valentino Fabris Valenti

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 21:40:22