C 中的多管道

发布于 2024-11-28 04:42:49 字数 2048 浏览 1 评论 0原文

我正在尝试用 C 实现多个管道, 解决方案应该是 for:

cmd1 | cmd2 | cmd3

和 for:

        |--- cmd2

cmd1    |--- cmd3

        |--- cmd4
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

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

char* args1[] = { "ls", NULL, NULL };
char* args2[] = { "ls", "-l", NULL };
char* args3[] = { "sort", NULL, NULL };
char* args4[] = { "wc", "-l", NULL };

int rc1 = execute_cmd(args1, 0);
//printf("rc1 = %d\n", rc1);

int rc2 = execute_cmd(args2, rc1);
//printf("rc2 = %d\n", rc2);

int rc3 = execute_cmd(args3, rc1);
//printf("rc3 = %d\n", rc3);

int rc4 = execute_cmd(args4, rc1);
//printf("rc4 = %d\n", rc4);

int buffer[1024];
int len = 0;

if (rc2) {
    while ((len = read(rc2, buffer, sizeof(buffer))) > 0) {
        write(STDERR_FILENO, "rc2\n", 4);
        write(STDERR_FILENO, &buffer, len);
    }
} else {
    printf(stderr, "ERROR\n");
}

if (rc3) {
    while ((len = read(rc3, buffer, sizeof(buffer))) > 0) {
        write(STDERR_FILENO, "rc3\n", 4);
        write(STDERR_FILENO, &buffer, len);
    }
} else {
    printf(stderr, "ERROR\n");
}

if (rc4) {
    while ((len = read(rc4, buffer, sizeof(buffer))) > 0) {
        write(STDERR_FILENO, "rc4\n", 4);
        write(STDERR_FILENO, &buffer, len);
    }
} else {
    printf(stderr, "ERROR\n");
}

return 0;
}

int execute_cmd(char** args, int fd_in) {

int pipefd[2];
pipe(pipefd);

if (fork() == 0) {
    close(pipefd[0]);

    dup2(pipefd[1], STDOUT_FILENO);
    dup2(pipefd[1], STDERR_FILENO);

    close(pipefd[1]);

    if (fd_in) {
        dup2(fd_in, 0);
    }

    execvp(*args, args);
    printf("failed to execute %s %s", *args, *args[0]);
} else {
    close(pipefd[1]);

    return pipefd[0];

}
}

程序的输出是不确定的,一旦我看到正确的结果,一旦我看到不同的结果。看起来 dup2 没有按我的预期运行,如果我多次 dup2 并且对于从结果文件描述符读取的每个文件描述符 - 看起来它对复制的文件描述符有影响?

如果它像我在设计中提到的那样工作,我需要对两者使用哪个系统调用?

I am trying to implement multiple pipes in C,
the solution should be both for:

cmd1 | cmd2 | cmd3

and for:

        |--- cmd2

cmd1    |--- cmd3

        |--- cmd4
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

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

char* args1[] = { "ls", NULL, NULL };
char* args2[] = { "ls", "-l", NULL };
char* args3[] = { "sort", NULL, NULL };
char* args4[] = { "wc", "-l", NULL };

int rc1 = execute_cmd(args1, 0);
//printf("rc1 = %d\n", rc1);

int rc2 = execute_cmd(args2, rc1);
//printf("rc2 = %d\n", rc2);

int rc3 = execute_cmd(args3, rc1);
//printf("rc3 = %d\n", rc3);

int rc4 = execute_cmd(args4, rc1);
//printf("rc4 = %d\n", rc4);

int buffer[1024];
int len = 0;

if (rc2) {
    while ((len = read(rc2, buffer, sizeof(buffer))) > 0) {
        write(STDERR_FILENO, "rc2\n", 4);
        write(STDERR_FILENO, &buffer, len);
    }
} else {
    printf(stderr, "ERROR\n");
}

if (rc3) {
    while ((len = read(rc3, buffer, sizeof(buffer))) > 0) {
        write(STDERR_FILENO, "rc3\n", 4);
        write(STDERR_FILENO, &buffer, len);
    }
} else {
    printf(stderr, "ERROR\n");
}

if (rc4) {
    while ((len = read(rc4, buffer, sizeof(buffer))) > 0) {
        write(STDERR_FILENO, "rc4\n", 4);
        write(STDERR_FILENO, &buffer, len);
    }
} else {
    printf(stderr, "ERROR\n");
}

return 0;
}

int execute_cmd(char** args, int fd_in) {

int pipefd[2];
pipe(pipefd);

if (fork() == 0) {
    close(pipefd[0]);

    dup2(pipefd[1], STDOUT_FILENO);
    dup2(pipefd[1], STDERR_FILENO);

    close(pipefd[1]);

    if (fd_in) {
        dup2(fd_in, 0);
    }

    execvp(*args, args);
    printf("failed to execute %s %s", *args, *args[0]);
} else {
    close(pipefd[1]);

    return pipefd[0];

}
}

The output of the program is not deterministic, once I see the right result, and once I saw different result. Looks like dup2 not function as I expected, if I dup2 multiple times and for each file descriptor read from the result file descriptor - it's look like it's influence on the copied file descriptor?

If it's work like I mention by design, which system call I need to use for both?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

无风消散 2024-12-05 04:42:49

是的,dup 和 dup2 创建相同管道的完全等效的句柄。如果多个进程(或线程)同时尝试使用重复/分叉描述符从管道中读取数据,则它们中的“随机”将首先获取数据,但写入管道的每个字节仅传递一次。

如果要将数据复制到多个不同的读取器,则必须显式编程 - 分叉一个子进程(或生成一个线程)以从一个传入管道读取一些数据,然后将其写入所有传出管道,然后继续循环直到到达 EOF。

Yes, dup and dup2 create completely equivalent handles to the same pipe. If several processes (or threads) simultaneously attempt to read from the pipe using duplicated/forked descriptors, a "random" of them will get to the data first, but each byte written to the pipe only gets delivered once.

If you want to copy data to multiple different readers, you have to program that explicitly -- fork a subprocess (or spawn a thread) to read some data from one incoming pipe, then write it to all of the outgoing ones, and continue in a loop until you reach EOF.

破晓 2024-12-05 04:42:49

对同一管道有多个句柄/引用会导致很多同步问题等。

例如,如果有 2 个子进程,其中一个发送“Hello\n”,然后发送“World\n”,另一个发送“Foo\n” " 然后 " 酒吧\n";那么你最终可能会得到“Hello\n World\n Foo\n Bar\n”或“Hello\n Foo\n World\n Bar”或“Foo\n Hello\n Bar\n World”等。输出最终是无序的(这会非常令人困惑)。

解决方案是使用不同的管道。

基本上,当主程序分叉时,它应该创建新的管道,这些管道将成为子进程的 STDOUT 和 STDERR。然后主程序将需要从所有新管道的末端读取并(可能)缓冲信息,以便主进程可以按特定顺序将子进程中的数据发送到其自己的 STDOUT/STDERR - 例如所有第一个子级的输出,然后是第二个子级的所有输出,然后是下一个子级的所有输出,等等。

主程序还可以添加额外的信息并进行一些格式化,以使其更清楚地了解正在发生的情况。对于上面的示例,您最终可能会得到:

Process A (exit status = 0, OK):
    Hello
    World
Process B (exit status = 1, Failed):
    Foo
    Bar

而不仅仅是:

 Hello
 World
 Foo
 Bar

对于输入(STDIN),我不知道您希望它如何工作。如果没有子进程需要 STDIN(最简单且最有可能的情况),那么您可以忽略它。如果每个子进程需要获取自己的主进程 STDIN 副本,那么您需要为每个子进程创建新管道以用作其 STDIN。

另一种选择是有一个“当前选择的子项”,这可能会变得更加复杂(特别是如果最终用户需要能够看到所选子项的输出,因为这意味着实现某种方式在“当前正在显示的”子项之间切换 - 例如,当用户选择不同的子项时,清除屏幕并显示该子项的待办事项)。

Having multiple handles/references to the same pipe will cause a lot of problems with synchronisation, etc.

For example, if there's 2 child processes where one sends " Hello\n" then " World\n" and the other sends " Foo\n" then " Bar\n"; then you could end up with " Hello\n World\n Foo\n Bar\n" or " Hello\n Foo\n World\n Bar" or " Foo\n Hello\n Bar\n World", etc. The output ends up unordered (which would be extremely confusing).

The solution is to use different pipes.

Basically, when the main program forks it should create new pipe/s that will become the child process' STDOUT and STDERR. Then the main program will need to read from its end of all the new pipes and (potentially) buffer the information, so that the main process can send the data from children to its own STDOUT/STDERR in a specific order - e.g. all of the first child's output, then all of the second child's output, then all of the next child's output, etc.

The main program could also add extra information and do some formatting to make it clearer what is happening. For the example above, you could end up with:

Process A (exit status = 0, OK):
    Hello
    World
Process B (exit status = 1, Failed):
    Foo
    Bar

Rather than just:

 Hello
 World
 Foo
 Bar

For input (STDIN) I've got no idea how you want it to work. If none of the child processes need STDIN (the easiest and most likely case) then you can ignore it. If each child process needs to get their own copy of the main process' STDIN then you'd need to create new pipes for each child to use as its STDIN.

The other alternative is to have a "currently selected child", which can become a lot more complex (especially if there's an end-user who needs to be able to see the output of the selected child, as that'd mean implementing some way of switching between "currently being displayed" children - e.g. when the user selects a different child, clear the screen and display that child's backlog).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文