使用管道后的问题恢复标准输出

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

使用在这里找到的建议:Restoring stdout after using dup我试图恢复标准输入和标准输出。但是,当使用printf检查stdout是否已恢复时,我无法读取输出。代码如下。将stdout恢复为1后,我尝试打印“完成”。

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

void main(int argv, char *argc) {
    int stdin_copy = dup(STDIN_FILENO);
    int stdout_copy = dup(STDOUT_FILENO);
    int testpipe[2];
    pipe(testpipe); 
    int PID = fork();
    if (PID == 0) { 
        dup2(testpipe[0], 0);
        close(testpipe[1]);
        execl("./multby", "multby", "3", NULL);// Just multiplies argument 3 with the number in testpipe.
    } else {
        dup2(testpipe[1], 1);
        close(testpipe[0]);
        printf("5");
        fclose(stdout);
        close(testpipe[1]);
        char initialval[100];
        read(testpipe[0], initialval, 100);
        fprintf(stderr, "initial value: %s\n", initialval);
        wait(NULL);
        dup2(stdin_copy, 0);
        dup2(stdout_copy, 1);
        printf("done");//was not displayed when I run code.
    }
}

但是,我在运行代码时没有看到“完成”。 (应该在15点之后完成)。这是我的输出:初始值:执行成功a:3b:5多人成功15

还原标准输出时我做错了什么?

c pipe stdout dup2 dup
2个回答
1
投票

您正在调用fclose(stdout);,它将关闭stdout FILE对象以及STDOUT_FILELO – stdout文件描述符(它们是两个不同的东西,链接在一起)。因此,当您稍后调用dup2(stdout_copy, 1);时,将还原文件描述符,但是FILE对象保持关闭状态,因此您无法使用它来输出任何内容。无法重新打开FILE对象以引用文件描述符1,因此最好的选择是删除fclose行。 dup2将关闭您要替换的文件描述符(因此您确实不需要单独关闭),并且应该能够看到输出


1 在某些系统上,可以将freopen与/ dev / fd一起使用,但是/ dev / fd是不可移植的


0
投票

一般说明

重申我之前在其他答案中所说的。

您在子进程中没有关闭足够的文件描述符。


拇指法则:如果您dup2()管道的一端到标准输入或标准输出,同时关闭两个由返回的原始文件描述符dup2()尽快地。特别是,在使用任何pipe()功能系列。

如果您将描述符与pipe()要么exec*()exec*()dup()


如果父进程不会通过以下方式与其任何子进程通信,管道,必须确保尽早关闭管道的两端足够(例如在等待之前),以便其子代可以接收EOF指示在读取(或获取SIGPIPE信号或在写),而不是无限期地阻止。即使父级在不使用dup()的情况下使用管道,也应通常至少会关闭管道的一端,这种情况极少发生用于在单个管道的两端进行读写的程序。

请注意,fcntl()选项fcntl(),并且F_DUPFDF_DUPFD_CLOEXECdup2()选项也可以进入讨论。

如果您使用O_CLOEXEC及其广泛的支持功能系列(总共21个功能),您将需要查看如何在生成的过程中关闭文件描述符(open(),等)。

请注意,使用open()比使用FD_CLOEXEC更安全由于多种原因。一种是,如果您要强制文件描述符大于通常,F_DUPFD_CLOEXEC是唯一可行的方法。另一个是如果fcntl()posix_spawn()相同(例如,两个posix_spawn()),则posix_spawn_file_actions_addclose()正确处理它(在复制posix_spawn_file_actions_addclose()之前不会关闭dup2(a, b))而单独的close(b); dup(a);dup2()严重失败。这是不太可能的,但不是不可能的情况。


代码分析

问题包含代码:

a

由于没有使用命令行参数,因此b行应为0。您还具有与常规约定相反的名称dup2()b-第一个参数通常称为a(参数计数),第二个参数通常称为close()(参数向量)。此外,第二个参数的类型应为dup()(如果您想使所有普通读者感到困惑,则应为#include <stdio.h> #include <unistd.h> void main(int argv, char *argc) { int stdin_copy = dup(STDIN_FILENO); int stdout_copy = dup(STDOUT_FILENO); int testpipe[2]; pipe(testpipe); int PID = fork(); if (PID == 0) { dup2(testpipe[0], 0); close(testpipe[1]); execl("./multby", "multby", "3", NULL);// Just multiplies argument 3 with the number in testpipe. } else { dup2(testpipe[1], 1); close(testpipe[0]); printf("5"); fclose(stdout); close(testpipe[1]); char initialval[100]; read(testpipe[0], initialval, 100); fprintf(stderr, "initial value: %s\n", initialval); wait(NULL); dup2(stdin_copy, 0); dup2(stdout_copy, 1); printf("done");//was not displayed when I run code. } } )。另请参见void main(int argv, char *argc) {

下一个值得讨论的代码块是:

int main(void)

这违反了经验法则。您还应该在argv之后放置错误处理代码。

argc

下一个要分析的代码块是:

argc

[理论上,您应该使用argv而不是char **argv,但是我对char **argc的使用持相当同情的态度(尤其是因为当我第一次学习C时,没有这样的符号常量)。您实际上确实关闭了管道的两端,但是我更希望看到在What should main() return in C and C++?调用之后两个管道都立即关闭,这符合经验法则。不带换行符的main()确实可以通过管道发送任何内容。它将 if (PID == 0) { dup2(testpipe[0], 0); close(testpipe[1]); execl("./multby", "multby", "3", NULL);// Just multiplies argument 3 with the number in testpipe. } 隐藏在I / O缓冲区中。

正如在他们的execl()中标识的if (PID == 0) { dup2(testpipe[0], STDIN_FILENO); close(testpipe[0]); close(testpipe[1]); execl("./multby", "multby", "3", (char *)NULL); fprintf(stderr, "failed to execute ./multby\n"); exit(EXIT_FAILURE); } 一样,dup2(testpipe[1], 1); close(testpipe[0]); printf("5"); fclose(stdout); close(testpipe[1]); 调用带来了很多麻烦。您可能应该简单地将其替换为STDOUT_FILENO

继续:

1

您没有检查1是否有效。没有它因EBADF而失败,因为在此之上您使用了dup2()。在printf()上打印的内容有一个未初始化的字符串-不好。实际上,如果要可靠地从孩子那里读取信息,则需要两个管道,一个用于父子之间的通信,另一个用于子对父母的通信。否则,不能保证父母不会读它写的东西。如果您等孩子死后才读书,那么孩子工作的机会就很大,但您不能总是依靠这样做。

两个5调用中的第一个是没有意义的;您没有更改标准输入的重定向(因此实际上Chris Dodd是不必要的)。第二个更改标准输出文件描述符的分配,但是您已经关闭了answer,因此没有简单的方法可以重新打开它以使fclose(stdout)正常工作。该消息应以换行符结尾-大多数fflush(stdout)格式的字符串应以换行符结尾,除非有意将其用于构建输出的零碎单行。但是,如果您注意返回值,您会发现它失败(char initialval[100]; read(testpipe[0], initialval, 100); fprintf(stderr, "initial value: %s\n", initialval); wait(NULL); dup2(stdin_copy, 0); dup2(stdout_copy, 1); printf("done"); ),并且很有可能再次找到read()

修复代码-脆弱的解决方案

close(testpipe[0]);提供此代码:

stderr

和此代码(作为dup2(),编译为stdin_copy):

stdout

这种组合几乎行不通-这不是一个弹性的解决方案。例如,我在printf()之后添加了换行符。孩子在printf()之后等待另一个字符来确定它已经完成了对数字的读取。它没有获得EOF,因为它已打开管道的写端以将响应发送给父级,即使它已挂起从管道读取,因此也永远不会对其进行写操作。但是因为它只尝试读取一个数字,所以可以。

输出为:

-1

修复代码-强大的解决方案

如果要处理任意数量的数字,则需要使用两个管道-这是完成任务的唯一可靠方法。当然,这也适用于传递给孩子的单个数字。

这里是修改后的errno == EBADF,会在阅读时循环播放:

multby.c

这是经过修改的#include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "Usage; %s number", argv[0]); return 1; } int multiplier = atoi(argv[1]); int number; if (scanf("%d", &number) == 1) printf("%d\n", number * multiplier); else fprintf(stderr, "%s: failed to read a number from standard input\n", argv[0]); return 0; } ,它使用两个管道并将3个数字写入子级,并返回三个结果。请注意,该组织无需在第三个数字之后插入换行符(尽管如果包含换行符也不会造成任何危害)。另外,如果您不满意,则数字列表中的第二个空格也是不必要的; pipe23.c不是第二个数字的一​​部分,因此扫描在pipe23之后停止。

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

int main(void)
{
    int stdout_copy = dup(STDOUT_FILENO);
    int testpipe[2];
    pipe(testpipe);
    int PID = fork();
    if (PID == 0)
    {
        dup2(testpipe[0], 0);
        dup2(testpipe[1], 1);
        close(testpipe[1]);
        close(testpipe[0]);
        execl("./multby", "multby", "3", NULL);// Just multiplies argument 3 with the number in testpipe.
        fprintf(stderr, "failed to execute ./multby (%d: %s)\n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }
    else
    {
        dup2(testpipe[1], 1);
        close(testpipe[1]);
        printf("5\n");
        fflush(stdout);
        close(1);
        wait(NULL);
        char initialval[100];
        read(testpipe[0], initialval, 100);
        close(testpipe[0]);
        fprintf(stderr, "initial value: [%s]\n", initialval);
        dup2(stdout_copy, 1);
        printf("done\n");
    }
}

[请注意,那里有很多对5的调用-处理两个管道所涉及的4个描述符中的每个都有两个。这是正常的。不小心关闭文件描述符很容易导致系统挂起。

运行此5的输出是这个,这就是我想要的:

initial value: [15
]
done
© www.soinside.com 2019 - 2024. All rights reserved.