【问题标题】:piping in c, 2 forks talk to mainc中的管道,2个叉子与main交谈
【发布时间】:2020-04-13 17:26:03
【问题描述】:

好吧,简短的故事是: 我有一个程序需要做 X,它做 0.25X。我使用 2 个叉子和 4 个管道,我不知道如何调试它。 (在 linux 环境中使用 eclipse c/c++)。

长篇大论: 我有一个程序需要从包含整数对的文本文件中计算 gcd(最大公约数)。这个程序有一个父亲(主要)和两个孩子(叉子),需要通过管道与父亲交谈。 (每个孩子 2 个管道。) 当我在 ubuntu 中编译并运行程序时,我没有收到任何错误,但程序没有完成它的任务。我不知道它在哪里/为什么会坏。我该如何调试呢?我在 eclipse c/c++ 中编码,调试器无法处理分叉。当我调试时,它正在从文件中读取所有数字(不计算 gcd),但是当我在 ubuntu 终端中运行时,它只读取第一行并中断。这是完整的代码:

int main(int argc, char **argv) {
if (argc != 2 || strcmp(argv[1], "--help") == 0) {
    fprintf(stderr, "‫‪usage: %s <FILE NAME>\n", argv[0]);
    return EXIT_FAILURE;
}

int pfd_child1_r[2], pfd_child1_w[2], pfd_child2_r[2], pfd_child2_w[2];
if (pipe(pfd_child1_r) == -1 || pipe(pfd_child1_w) == -1
        || pipe(pfd_child2_r) == -1 || pipe(pfd_child2_w) == -1) {
    perror("cannot pipe()");
    return EXIT_FAILURE;
}

createChilds(pfd_child1_r, pfd_child1_w, pfd_child2_r, pfd_child2_w);

FILE *fp = fopen(argv[1], "r");
if (fp == NULL) {
    perror("fopen(): ");
    return EXIT_FAILURE;
}

char line[100];
char *token;
int numbers[2], num, line_count = 1, counter = 0, result = 0;

while (fgets(line, sizeof(line), fp) != NULL) {
    token = strtok(line, " ");
    while (token != NULL) {
        num = atoi(token);
        if (num < 1 || counter == 2) {
            fprintf(stderr, "‫‪illegal‬‬ ‫‪input‬‬ at line %d\n",
                    line_count);
            return EXIT_FAILURE;
        }
        numbers[counter] = num;
        counter++;
        token = strtok(NULL, " ");
    }
    counter = 0;
    if (line_count % 2 == 0) { // use first child
        write(pfd_child1_w[1], &numbers[0], sizeof(int));
        write(pfd_child1_w[1], &numbers[1], sizeof(int));
    } else { // use second child
        write(pfd_child2_w[1], &numbers[0], sizeof(int));
        write(pfd_child2_w[1], &numbers[1], sizeof(int));
    }

    if (line_count > 1) { // after first run alternate to get result
        if (line_count % 2 == 0) { // read from second child
            read(pfd_child2_r[0], &result, sizeof(int));
            printf("%d %d\t\tgcd: %d\n", numbers[0], numbers[1], result);
        } else { // read from first child
            read(pfd_child1_r[0], &result, sizeof(int));
            printf("%d %d\t\tgcd: %d\n", numbers[0], numbers[1], result);
        }
    }

    line_count++;
}

fclose(fp);
return EXIT_SUCCESS;
}

void createChilds(int pfd_child1_r[2], int pfd_child1_w[2], int pfd_child2_r[2],

    int pfd_child2_w[2]) {

switch (fork()) {
case -1:
    perror("cannot fork()");
    exit(EXIT_FAILURE);

case 0: /* First child: */
    if (close(pfd_child1_r[0]) == -1) { /* Read end is unused */
        perror("cannot close()");
        exit(EXIT_FAILURE);
    }

    if (close(pfd_child1_w[1]) == -1) { /* Write end is unused */
        perror("cannot close()");
        exit(EXIT_FAILURE);
    }

    /* Duplicate stdout on write end of pipe; close duplicated descriptor */

    if (pfd_child1_w[1] != STDOUT_FILENO) { /* Defensive check */
        if (dup2(pfd_child1_r[1], STDOUT_FILENO) == -1) {
            perror("cannot dup2()");
            exit(EXIT_FAILURE);
        }
        if (close(pfd_child1_r[1]) == -1) {
            perror("cannot close()");
            exit(EXIT_FAILURE);
        }
    }

    /* Duplicate stdin on read end of pipe; close duplicated descriptor */

    if (pfd_child1_w[1] != STDIN_FILENO) { /* Defensive check */
        if (dup2(pfd_child1_w[0], STDIN_FILENO) == -1) {
            perror("cannot dup2()");
            exit(EXIT_FAILURE);
        }
        if (close(pfd_child1_w[0]) == -1) {
            perror("cannot close()");
            exit(EXIT_FAILURE);
        }
    }
    execlp("./v1_child", "./v1_child", NULL); /* Writes to pipe */
    exit(EXIT_SUCCESS);

default: /* Parent go to next child */
    break;
}

switch (fork()) {
case -1:
    perror("cannot fork()");
    exit(EXIT_FAILURE);

case 0: /* Second child: exec 'wc' to read from pipe */
    if (close(pfd_child2_r[0]) == -1) { /* Read end is unused */
        perror("cannot close()");
        exit(EXIT_FAILURE);
    }

    if (close(pfd_child2_w[1]) == -1) { /* Write end is unused */
        perror("cannot close()");
        exit(EXIT_FAILURE);
    }

    /* Duplicate stdout on write end of pipe; close duplicated descriptor */

    if (pfd_child2_w[1] != STDOUT_FILENO) { /* Defensive check */
        if (dup2(pfd_child2_r[1], STDOUT_FILENO) == -1) {
            perror("cannot dup2()");
            exit(EXIT_FAILURE);
        }
        if (close(pfd_child2_r[1]) == -1) {
            perror("cannot close()");
            exit(EXIT_FAILURE);
        }
    }

    /* Duplicate stdin on read end of pipe; close duplicated descriptor */

    if (pfd_child2_w[1] != STDIN_FILENO) { /* Defensive check */
        if (dup2(pfd_child2_w[0], STDIN_FILENO) == -1) {
            perror("cannot dup2()");
            exit(EXIT_FAILURE);
        }
        if (close(pfd_child2_w[0]) == -1) {
            perror("cannot close()");
            exit(EXIT_FAILURE);
        }
    }
    execlp("./v1_child", "./v1_child", NULL); /* Writes to pipe */
    exit(EXIT_SUCCESS);

default: /* Parent falls through */
    break;
}

/* Parent closes unused file descriptors for pipe */

if (close(pfd_child1_r[1]) == -1 || close(pfd_child1_w[0]) == -1
        || close(pfd_child2_r[1]) == -1 || close(pfd_child2_w[0]) == -1) {
    perror("cannot close()");
    exit(EXIT_FAILURE);
}

第二个文件是 gcd 文件,我还没有完成它,应该继续获取数字的循环不存在。但我只想让第一行正常工作,然后我会修复其余的。

int gcd(int n1, int n2) {
    if (n2 == 0)
        return n1;
    return gcd(n2, n1 % n2);
}

int main(int argc, char **argv) {

    int n1, n2, result;
    if (scanf("%d %d", &n1,&n2) != 2) {
        fprintf(stderr, "error reading numbers in child\n");
        return -1;
    }
    if (n1 > n2)
        result = gcd(n1, n2);
    else
        result = gcd(n2,n1);
    printf("%d", result);
}

【问题讨论】:

  • 如果用户请求--help,这不是错误,不应将使用消息写入stderr。 (cmd --help | less 的频率是多少?)。
  • 防御检查正在检查错误的描述符。不过,只要标准输入和标准输出都存在,它们就不会造成伤害。

标签: c linux process pipe


【解决方案1】:

如何调试

一种简单的调试方法总是将fprintf(stderr, "...") 语句添加到子程序中。然后你就可以运行程序,也可以看看子进程在做什么。

传递价值

由于您重定向标准输入和标准输出并在计算 gcd 的 v1_child 程序中使用 sscanf/printf,我假设您希望将值作为字符串传输。

一种简单的方法是使用 fprintf 将 int 写入格式化字符串。您可以使用 fdopen 将流与现有管道文件描述符相关联。

因此,您必须将数字从字符串转换为字符串。

可变长度数据和缓冲 I/O

如果您使用字符串来传输值,则每对值的长度都是可变的。通常,在 C 程序中使用换行符来表示完整的输入记录。

读/写整行的另一个原因是读/写调用也只能传输部分字节数。因此,您必须知道输入记录何时完成。另一种方法是使用二进制格式,它会自动表示具有固定长度的格式。

通过使用流处理缓冲 I/O,使用 fflush 可以确保通过流的底层写入函数写入所有缓冲数据。

函数

可以将功能划分为多个功能,以使流程更易于理解。

可能的改进

这也许已经是一个开始。

另一个可能的改进是使用 strtol 代替 atoi,因为 atoi 不执行错误检查。类似的 sscanf 不会报转换错误(例如行尾的非数字字符),至少我们看一下分配的输入项的数量。

大概还有提高代码可读性的可能性。

使用 waitpid 可以在父级中检查子级的退出状态代码。

计划

您的代码在上述几点稍作修改后可能如下所示:

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

void create_pipe(int *);
void close_fd(int);
int child(const int *, const int *);
int read_input_line(FILE *fp, char *line, int max, int *numbers, int line_count);
void write_to_child(FILE *fp, const int *numbers);
int read_from_child(FILE *fp);


int main(int argc, char *argv[]) {

    if (argc != 2 || strcmp(argv[1], "--help") == 0) {
        fprintf(stderr, "‫‪usage: %s <FILE NAME>\n", argv[0]);
        return EXIT_FAILURE;
    }

    int pfd_child1_r[2];
    int pfd_child1_w[2];
    int pfd_child2_r[2];
    int pfd_child2_w[2];

    create_pipe(pfd_child1_r);
    create_pipe(pfd_child1_w);
    create_pipe(pfd_child2_r);
    create_pipe(pfd_child2_w);

    pid_t pid1 = fork();

    if (pid1 == 0) { //child 1
        close_fd(pfd_child2_r[0]);
        close_fd(pfd_child2_r[1]);
        close_fd(pfd_child2_w[0]);
        close_fd(pfd_child2_w[1]);
        return child(pfd_child1_r, pfd_child1_w);
    } else if (pid1 > 0) {
        close_fd(pfd_child1_r[1]);
        close_fd(pfd_child1_w[0]);

        pid_t pid2 = fork();
        if (pid2 == 0) { //child 2
            close_fd(pfd_child1_r[0]);
            close_fd(pfd_child1_w[1]);
            return child(pfd_child2_r, pfd_child2_w);
        } else if (pid2 > 0) {
            close_fd(pfd_child2_r[1]);
            close_fd(pfd_child2_w[0]);

            FILE *fp_child1_w = fdopen(pfd_child1_w[1], "w");
            FILE *fp_child2_w = fdopen(pfd_child2_w[1], "w");
            FILE *fp_child1_r = fdopen(pfd_child1_r[0], "r");
            FILE *fp_child2_r = fdopen(pfd_child2_r[0], "r");

            if (!fp_child1_w || !fp_child2_w || !fp_child1_r || !fp_child2_r) {
                perror("fdopen() failed");
                return EXIT_FAILURE;
            }

            FILE *fp = fopen(argv[1], "r");
            if (fp == NULL) {
                perror("fopen(): ");
                return EXIT_FAILURE;
            }

            char line[100];
            int numbers[2], line_count = 0;
            while (read_input_line(fp, line, sizeof(line), numbers, line_count) == 2) {
                if (line_count % 2 == 0) {
                    write_to_child(fp_child1_w, numbers);
                } else {
                    write_to_child(fp_child2_w, numbers);
                }

                if (line_count % 2 == 0) {
                    int result = read_from_child(fp_child1_r);
                    printf("%d %d\t\tgcd: %d\n", numbers[0], numbers[1], result);
                } else {
                    int result = read_from_child(fp_child2_r);
                    printf("%d %d\t\tgcd: %d\n", numbers[0], numbers[1], result);
                }
                line_count++;
            }

            //fclose closes also associated file descriptor
            fclose(fp_child1_w);
            fclose(fp_child2_w);
            fclose(fp_child1_r);
            fclose(fp_child2_r);

            fclose(fp);
            return EXIT_SUCCESS;
        } else {
            perror("second fork failed");
            return EXIT_FAILURE;
        }
    } else {
        perror("first fork failed");
        return EXIT_FAILURE;
    }
}


int read_input_line(FILE *fp, char *line, int max, int *numbers, int line_count) {
    char *token;
    int num, counter = 0;

    line[0] = '\0';
    if (fgets(line, max, fp) != NULL) {
        token = strtok(line, " ");
        while (token != NULL) {
            num = atoi(token);
            if (num < 1 || counter == 2) {
                fprintf(stderr, "‫‪illegal‬‬ ‫‪input‬‬ at line %d\n", line_count + 1);
                exit(EXIT_FAILURE);
            }
            numbers[counter] = num;
            counter++;
            token = strtok(NULL, " ");
        }
    }
    return counter;
}

int read_from_child(FILE *fp) {
    char buf[128];
    int result = -1;
    if (fgets(buf, sizeof(buf), fp)) {
        if (sscanf(buf, "%d", &result) == 1)
            return result;
    }
    return -1;
}

void write_to_child(FILE *fp, const int *numbers) {
    fprintf(fp, "%d %d\n", numbers[0], numbers[1]);
    fflush(fp);
}

int child(const int *pfd_child_r, const int *pfd_child_w) {
    dup2(pfd_child_r[1], STDOUT_FILENO);
    dup2(pfd_child_w[0], STDIN_FILENO);
    close_fd(pfd_child_r[0]);
    close_fd(pfd_child_r[1]);
    close_fd(pfd_child_w[0]);
    close_fd(pfd_child_w[1]);
    execlp("./v1_child", "./v1_child", NULL);
    fprintf(stderr, "execution of v1_child failed\n");
    exit(EXIT_FAILURE);
}

void create_pipe(int *fd) {
    if (pipe(fd) == -1) {
        perror("cannot pipe()");
        exit(EXIT_FAILURE);
    }
}

void close_fd(int fd) {
    if (close(fd) == -1) {
        perror("cannot close()");
        exit(EXIT_FAILURE);
    }
}

对应的 v1_child.c 可能如下所示:

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

int gcd(int n1, int n2) {
    if (n2 == 0)
        return n1;
    return gcd(n2, n1 % n2);
}

int main(void) {

    int n1, n2, result;
    char buf[128];
    while(fgets(buf, sizeof(buf), stdin)) {
        if (sscanf(buf, "%d %d", &n1, &n2) != 2) {
            fprintf(stderr, "error reading numbers in child\n");
            return -1;
        }
        if (n1 > n2)
            result = gcd(n1, n2);
        else
            result = gcd(n2, n1);
        printf("%d\n", result);
        fflush(stdout);
    }
    return EXIT_SUCCESS;
}  

测试

随着

的输入
5 25
49 14
64 462
1155 84

输出将是

5 25        gcd: 5
49 14       gcd: 7
64 462      gcd: 2
1155 84     gcd: 21

【讨论】:

  • 哇。非常感谢!我真的很感谢花时间帮助我。我会逐行确保我理解所有内容,但这太棒了,再次感谢你。
  • 嗨,朋友,我有一些后续我想解决的问题: 1. 为什么必须使用 fflush?我想如果你打印换行符(“\n”)它会自动发送输入并清理缓冲区? 2. 在 read_line 函数中插入 line[0] = '\0'。这是为什么?如果我删除这行代码,我看不出有什么不同。 3. 最后一件事是.. 什么是我不能使用 fdopen,而想使用 write() 和 read() 系统调用?我无法在此评论中添加我的代码,但您至少可以指出版本之间的差异吗?再次感谢!
  • 1) 流通常仅在引用交互式终端会话时才进行行缓冲。在我们的例子中,流与管道相关联,因此它是完全缓冲的,我们需要刷新缓冲区。 2) 由于我们不评估调用方的行,如果我们返回 counter!=2,我们就不必这样做。但是如果我们想稍后在调用方使用 line,例如对于调试输出,我们必须考虑如果检测到文件结束,fgets 将不会设置其缓冲区。
  • 3) 当然可以只使用读写。例如,您可以使用 sprintf 将格式化为字符串的数字输出,然后使用 write 发送该字符串。同样的,当读取一个字节块时,你可以检查是否包含了`\n'。您还必须处理部分读取的情况。考虑到这一点,使用 fprintf 和 fgets 的流会稍微方便一些。
【解决方案2】:

您将二进制值写入管道,但尝试读取文本表示。因此,要么在父级中使用printf(如dprintf)的变体,要么(更好)在子级中使用read(和write)。

【讨论】:

    猜你喜欢
    • 2018-05-23
    • 2017-07-07
    • 1970-01-01
    • 2016-11-15
    • 2014-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-22
    相关资源
    最近更新 更多