【问题标题】:Pipes between parent and two children in CC中父母和两个孩子之间的管道
【发布时间】:2015-01-18 18:58:30
【问题描述】:

我正在尝试编写一个使用管道在父级和两个子级之间发送信息的 C 程序。该程序的目标是为字符串实现类似于归并排序的功能。我读了字符串的数量,然后是字符串。字符串在 2 个孩子之间递归分配,直到每个孩子只有一个字符串。我必须重定向孩子的标准输入以从父母的标准输出中读取。

由于某种原因,没有一个孩子比第一个字符串读得更多。 我该如何解决这个问题?

int main(int argc, char * argv[]) {
    int nrrows = 0;
    char * buffer = NULL;
    size_t n = 0;
    getline(&buffer, &n, stdin);
    char * endptr;
    nrrows = strtol(buffer, &endptr, 10);

    char rows[nrrows][MAX_LEN];
    int i = 0;
    n = 0;
    while(i < nrrows) {
        char * row = NULL;
        getline(&row, &n, stdin);   
        strcpy(rows[i], row);
        i++;
    }

    if(nrrows == 1) {
        fprintf(stderr, "%s", rows[0]);
        return 0;   
    }

    int fdcp1[2];
    int fdcp2[2];
    if(pipe(fdcp1) < 0) {
        fprintf(stderr, "pipe unsuccessfull\n");
        return EXIT_FAILURE;
    }
    if(pipe(fdcp2) < 0) {
        fprintf(stderr, "pipe unsuccessfull\n");
        return EXIT_FAILURE;
    }

    pid_t chpid1 = fork();
    if(chpid1 < 0) {
        fprintf(stderr, "fork unsuccessfull\n");
        return EXIT_FAILURE;
    }
    else if(chpid1 == 0) {
        close(fdcp2[0]);
        close(fdcp2[1]);

        close(fdcp1[1]);
        dup2(fdcp1[0], STDIN_FILENO);
        execlp("./forksort", "child1", NULL);
    }else {
        close(fdcp1[0]);
        dup2(fdcp1[1], STDOUT_FILENO);

        double half = (nrrows / 2);
        int h = half;
        char b[2];
        b[0] = '0' + h;
        b[1] = '\n';
        write(fdcp1[1], b, sizeof(b));

        for(i = 0; i < h; i ++) {
            rows[i][strlen(rows[i])] = '\0';
            write(fdcp1[1], rows[i], sizeof(rows[i]));
        }

        pid_t chpid2 = fork();
        if(chpid2 < 0) {
            fprintf(stderr, "fork unsuccessfull\n");
            return EXIT_FAILURE;
        }else if(chpid2 == 0) {
            close(fdcp1[0]);
            close(fdcp1[1]);

            close(fdcp2[1]);
            dup2(fdcp2[0], STDIN_FILENO);
            execlp("./forksort", "child2", NULL);
        }else {
            close(fdcp2[0]);
            dup2(fdcp2[1], STDOUT_FILENO);
            half = (nrrows / 2);
            h = half;
            char b[2];
            b[0] = '0' + (nrrows - h);
            b[1] = '\n';
            write(fdcp2[1], b, sizeof(b));

            for(i = h; i < nrrows; i ++) {
                rows[i][strlen(rows[i])] = '\0';
                write(fdcp2[1], rows[i], sizeof(rows[i]));
            }
        }
    }
    return 0;
}

【问题讨论】:

  • 这些行pid_t chpid2 = fork(); pid_t chpid1 = fork(); 不会导致创建第一个孩子的额外孩子吗? chpid2 和 chpid1 将由父级创建,但另一个 chpid1 将由父级的 chpid2 创建。
  • 好的,我知道。但他们 child1 仍然不会阅读。
  • 抱歉,我没有时间也没有意愿深入研究代码,但可能管道刚刚被另一个孩子关闭。只需修复分叉代码并检查它是否正在修复行为。
  • @EugeneSh。我更新了我的代码并修复了导致更多孩子的问题。您现在可以看一下,因为孩子们还没有正确阅读吗?
  • 使用write() 切换到低级I/O 对于您正在尝试做的事情来说并不是一个特别好的方法。有一些您没有处理的复杂情况,您可以通过使用流 I/O 来避免任何需要处理的情况。例如,您当前的问题可能与您向子进程写入的数据比您想象的要多的事实有关,其形式是每个换行符后面的空字节加上后续的垃圾。 (如果你 很幸运。)

标签: c pipe exec fork parent-child


【解决方案1】:

修改与打开的流相关联的文件描述符是个坏消息。我认为它很可能会给您带来麻烦,而且,这里没有必要这样做。父级应改为使用fdopen() 在其管道末端打开新流,并通过这些而不是通过标准流与其子级进行 I/O。除了更安全之外,它还保留了进程的原始标准流可供它与其父进程通信。

使用这种方法,您甚至可以流式传输要在进程之间来回排序的字符串,而不是在每个进程的内存中冗余缓冲它们的块。例如,您可能会这样做:

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

int main(int argc, char * argv[]) {
    char * buffer = NULL;
    size_t buflen = 0;
    int nrrows;
    int fdpc1[2];
    int fdcp1[2];
    int fdpc2[2];
    int fdcp2[2];
    pid_t chpid1;
    pid_t chpid2;
    FILE *pipeout;
    FILE *pipein1;
    FILE *pipein2;
    int half;
    int i;

    fprintf(stderr, "%s!!!!!!!!!!!!!!!!!\n", argv[0]);

    getline(&buffer, &buflen, stdin);
    fprintf(stderr, "number: %s from %s\n", buffer, argv[0]);
    nrrows = strtol(buffer, NULL, 10);

    if(nrrows <= 0) {
        fprintf(stderr, "This is not a valid >0 number\n");
        return EXIT_FAILURE;
    } else if (nrrows == 1) {
        /* ... read and echo back the one row ... */
        getline(&buffer, &buflen, stdin);
        fprintf(stderr, "%s", buffer);
        return EXIT_SUCCESS;
    }

    /* There are at least two rows to sort */

    if (pipe(fdcp1) < 0) {
        fprintf(stderr, "pipe unsuccessfull\n");
        return EXIT_FAILURE;
    }
    if (pipe(fdpc1) < 0) {
        fprintf(stderr, "pipe unsuccessfull\n");
        return EXIT_FAILURE;
    }

    chpid1 = fork();
    if (chpid1 == 0) {
        /* this is child process 1 */
        close(fdcp1[1]);
        close(fdpc1[0]);
        dup2(fdcp1[0], STDIN_FILENO);
        close(fdcp1[0]);
        dup2(fdpc1[1], STDOUT_FILENO);
        close(fdpc1[1]);
        execlp("./forksort", "child1", NULL);
    } else if (chpid1 < 0) {
        fprintf(stderr, "fork unsuccessfull\n");
        return EXIT_FAILURE;
    }

    /* this is the parent process */

    close(fdcp1[0]);
    close(fdpc1[1]);

    if (pipe(fdcp2) < 0) {
        fprintf(stderr, "pipe unsuccessfull\n");
        return EXIT_FAILURE;
    }
    if (pipe(fdpc2) < 0) {
        fprintf(stderr, "pipe unsuccessfull\n");
        return EXIT_FAILURE;
    }

    chpid2 = fork();
    if (chpid2 == 0) {
        /* this is child process 2 */
        close(fdcp1[1]);
        close(fdpc1[0]);
        close(fdcp2[1]);
        close(fdpc2[0]);
        dup2(fdcp2[0], STDIN_FILENO);
        close(fdcp2[0]);
        dup2(fdpc2[1], STDOUT_FILENO);
        close(fdpc2[1]);
        execlp("./forksort", "child2", NULL);
    } else if (chpid2 < 0) {
        fprintf(stderr, "fork unsuccessfull\n");
        return EXIT_FAILURE;
    }

    /* this is the parent process */

    close(fdcp2[0]);
    close(fdpc2[1]);

    /* copy the first half of the lines from input to child 1 */

    pipeout = fdopen(fdcp1[1], "w");
    if (pipeout == NULL) {
        fprintf(stderr, "fdopen unsuccessful\n");
        return EXIT_FAILURE;
    }

    half = nrrows / 2;
    fprintf(pipeout, "%d\n", half);
    for (i = 0; i < half; i += 1) {
        getline(&buffer, &buflen, stdin);
        fprintf(stderr,"row[%d] from %s: %s", i, argv[0], buffer);
        fputs(buffer, pipeout);
    }
    fclose(pipeout);

    /* copy the second half of the lines from input to child 2 */

    pipeout = fdopen(fdcp2[1], "w");
    if (pipeout == NULL) {
        fprintf(stderr, "fdopen unsuccessful\n");
        return EXIT_FAILURE;
    }

    fprintf(pipeout, "%d\n", nrrows - half);
    for (; i < nrrows; i += 1) {
        getline(&buffer, &buflen, stdin);
        fprintf(stderr,"row[%d] from %s: %s", i, argv[0], buffer);
        fputs(buffer, pipeout);
    }
    fclose(pipeout);

    /* now read and merge sorted lines from the children */

    pipein1 = fdopen(fdpc1[0], "r");
    pipein2 = fdopen(fdpc2[0], "r");
    if (pipein1 == NULL || pipein2 == NULL) {
        fprintf(stderr, "fdopen unsuccessful\n");
        return EXIT_FAILURE;
    }

    /* ... */

    fclose(pipein1);
    fclose(pipein2);

    return 0;
}

【讨论】:

  • 在我有限的测试中,这似乎表明所有子进程都成功通信。
  • 我更新了我的代码,并详细说明了我的要求。
  • 如果不清楚:每个父级在其管道末端打开新流以与其子级对话;另一方面,这些管道的子端被映射到它们的标准流。
  • 更新以演示在将管道 FD 复制到标准 I/O FD 后关闭管道 FD。这是一个很好的一般做法,尽管在这种情况下对我来说似乎没有必要。
猜你喜欢
  • 2017-08-26
  • 1970-01-01
  • 2018-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-20
  • 1970-01-01
相关资源
最近更新 更多