通过管道将一个子级的标准输出发送到另一个子级的标准输出

问题描述 投票:0回答:1

我正在做 OSTEP 的作业,并做以下练习:

编写一个程序,创建两个子级,并将其中一个的标准输出连接到另一个的标准输入,使用 pipeline() 系统调用。

我是这样理解我需要做什么的:要连接第一个子进程的标准输出,我应该在父进程中使用

pipe(pipe_name)
制作管道,执行一个
fork()
创建第一个子进程,然后使用
dup2( pipe_name[1],STDOUT_FILENO);
更改标准输出流。之后,任何
printf
语句都会将文本发送到管道。然后我返回父进程并执行另一个
fork()
创建第二个子进程,并执行
dup2( pipe_name[0],STDIN_FILENO);
更改输入。

这是正确的吗?如果是这样,第二个孩子如何读取管道中的内容?我正在努力做

char words;
read(pipe_name[0], words, 1000);

但是我的输出只是

0
,而不是我之前打印的字符串。我的 C 语法有问题吗,还是更基础(我只是在这样做时学习 C,所以前者是肯定的可能性)。

这是我的完整代码:

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

int
main(int argc, char *argv[])
{   int pipe_name[2];
    printf("hello world (pid:%d)\n", (int) getpid());
    if(pipe(pipe_name)<0){ printf("mission failed! We'll gettem next time...\n");
    } else {
    int rc = fork();
    if (rc < 0) {
        // fork failed; exit
        fprintf(stderr, "fork failed\n");
        exit(1);
    } else if (rc == 0) {
        // child (new process)
        printf("hello, I am child (pid:%d)\n", (int) getpid());
        dup2( pipe_name[1],STDOUT_FILENO);
        printf("hello, I am child (pid:%d)\n", (int) getpid());
    } else {
        // parent goes down this path (original process)
        wait(NULL);

        int rc2 = fork();

                if (rc2 < 0) {
                    // fork failed; exit
                    fprintf(stderr, "fork failed\n");
                    exit(1);
                } else if (rc2 == 0) {
                    
                    dup2( pipe_name[1],STDIN_FILENO);
                    char words;
                    read(STDIN_FILENO, words, 1000);
                    printf("My sibling says:\n    %d\n", words);
                } else { wait(NULL);
                    // parent goes down this path (original process)
                    printf("hello, I am parent of %d (pid:%d)\n",
                    rc2, (int) getpid());
                }
    }
    return 0;
    }}

输出是

hello world (pid:30421)
hello, I am child (pid:30422)
My sibling says:
    0
hello, I am parent of 30423 (pid:30421)
c operating-system pipe
1个回答
0
投票

有两个主要问题影响您的代码:

  • 您打算如何将最多 1000 个字符读取为单个字符

    char words; read(STDIN_FILENO, words, 1000);
    
  • 如果读取字符,则需要将其格式化为字符串(

    %s
    ,而不是
    %d
    )。

  • 将管道的写入端复制到标准输入意味着读取操作将失败

    dup2(pipe_name[1],STDIN_FILENO);
    

    1 必须为 0。

  • 管道上写入的内容不会是空终止字符串;它将是一个字节数组。你必须确保它是一个字符串。

确保立即关闭管道未使用的末端是个好主意。这在这里并不是什么大问题,因为您只执行一次读取操作,而不是读取到 EOF。尽管如此,从一开始就养成良好的习惯还是有好处的。

此外,应在

stderr
上报告错误,并且当出现问题时程序应以非零状态退出。

这是解决这些问题的修订代码:

/* SO 7818-3904 */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

static void wait_loop(void)
{
    int corpse;
    int status;

    while ((corpse = wait(&status)) != -1)
    {
        fprintf(stderr, "%d: PID %d exited with status 0x%.4X\n",
                (int)getpid(), corpse, status);
    }
}

int
main(void)
{
    int pipe_name[2];
    printf("hello world (pid:%d)\n", (int)getpid());
    if (pipe(pipe_name) < 0)
    {
        fprintf(stderr, "mission failed! We'll gettem next time...\n");
        exit(1);
    }

    int rc1 = fork();
    if (rc1 < 0)
    {
        fprintf(stderr, "fork 1 failed\n");
        exit(1);
    }
    else if (rc1 == 0)
    {
        printf("hello, I am child 1 (pid:%d)\n", (int)getpid());
        dup2(pipe_name[1], STDOUT_FILENO);
        close(pipe_name[0]);
        close(pipe_name[1]);
        printf("hello, I am child 1 (pid:%d)\n", (int)getpid());
    }
    else
    {
        wait_loop();    /* Not really necessary here */
        int rc2 = fork();
        if (rc2 < 0)
        {
            fprintf(stderr, "fork 2 failed\n");
            exit(1);
        }
        else if (rc2 == 0)
        {
            close(pipe_name[1]);
            printf("hello, I am child 2 (pid:%d)\n", (int)getpid());
            dup2(pipe_name[0], STDIN_FILENO);
            close(pipe_name[0]);
            char words[1000];
            int nbytes = read(STDIN_FILENO, words, sizeof(words));
            if (nbytes > 0)
            {
                words[nbytes] = '\0';
                printf("My sibling says:\n%d: [%s]\n", nbytes, words);
            }
            else
            {
                fprintf(stderr, "%d: Got value %d from read(): %d %s\n",
                        (int)getpid(), nbytes, errno, strerror(errno));
                exit(1);
            }
        }
        else
        {
            close(pipe_name[0]);
            close(pipe_name[1]);
            wait_loop();
            // parent goes down this path (original process)
            printf("%d: hello, I am parent of %d and %d\n",
                   (int)getpid(), rc1, rc2);
        }
    }
    return 0;
}

输出示例:

hello world (pid:23510)
hello, I am child 1 (pid:23511)
23510: PID 23511 exited with status 0x0000
hello, I am child 2 (pid:23512)
My sibling says:
32: [hello, I am child 1 (pid:23511)
]
23510: PID 23512 exited with status 0x0000
23510: hello, I am parent of 23511 and 23512

请注意,我将等待代码放入函数中并打印出返回的值 - 我认为

wait(NULL)
是草率的表现。您的进程可能有它不知道的子进程(不太可能,但有可能)。获取输出有助于调试。

第一个等待循环并不是真正必要的,尽管在这种情况下它并没有什么害处。但是,如果孩子们传输了足够的数据(目前超过 64 KiB,但 POSIX 只保证一个管道可以容纳 4 KiB),那么早期的等待循环将阻止读取器开始,而写入器将永远不会完成,因为它是等待某个进程清空管道,但这不会发生。

© www.soinside.com 2019 - 2024. All rights reserved.