【问题标题】:Working with pipes in Unix C在 Unix C 中使用管道
【发布时间】:2014-06-02 04:14:11
【问题描述】:

我在使用 C 中的管道时遇到严重问题。我应该从命令行接收参数(例如:./myprogram 123 45 67),一次将一个字符读入缓冲区,发送字符给子进程进行统计,然后返回给父进程读取的字符总数。我的代码如下(注意:cmets 是我应该做的):

// Characters from command line arguments are sent to child process
// from parent process one at a time through pipe.
// Child process counts number of characters sent through pipe.
// Child process returns number of characters counted to parent process.
// Parent process prints number of characters counted by child process.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

static int toChild[2];
static int fromChild[2];
static char buffer;

int main(int argc, char **argv)
{
    int status;
    int nChars = 0;
    pid_t   pid;

    pipe(toChild);
    pipe(fromChild);

    if ((pid = fork()) == -1) {
        printf("fork error %d\n", pid);
        return -1;
    }
    else if (pid == 0) {
        close(toChild[1]);
        close(fromChild[0]);
        // Receive characters from parent process via pipe
        // one at a time, and count them.

        int count = 0;
        printf("child about to read\n");
        while(read(toChild[0], &buffer, 1)){
            count++;
        }
        // Return number of characters counted to parent process.

        write(fromChild[1], &count, sizeof(count));
        close(toChild[0]);
        close(fromChild[1]);
        printf("child exits\n");
    }
    else {
        close(toChild[0]);
        close(fromChild[1]);
        // -- running in parent process --
        printf("CS201 - Assignment 3 - Chris Gavette\n");

        write(toChild[1], &argv[1], 1); 

        // Send characters from command line arguments starting with
        // argv[1] one at a time through pipe to child process.

        read(fromChild[0], &nChars, 1);

        // Wait for child process to return. Reap child process.
        // Receive number of characters counted via the value
        // returned when the child process is reaped.
        close(toChild[1]);
        close(fromChild[0]);
        waitpid(pid, &status, 0);

        printf("child counted %d chars\n", nChars);
        printf("parent exits\n");
        return 0;
    }
}

即使我关闭了两个管道的两端,子进程似乎也挂起。

【问题讨论】:

  • 您有两个 fork 调用。你知道的,对吧?第二个盲目地覆盖从第一个返回的 pid。取出第一个。然后考虑您正在执行的所有读/写调用,并问自己来回发送的内容有多大。例如:write(toChild[1], &amp;count, 1)。嗯,countint;为什么你只发送一个字节而不是 sizeof(count) ?而且您的子循环退出条件充其量是不确定的。最后,以防您没有意识到;您应该为此使用 两个 管道描述符对;一个都没有。
  • 啊哈,谢谢。这解决了两次调用的问题。
  • 我发现在发布这个问题后一两分钟我需要两个文件描述符。我觉得很愚蠢,因为这应该很明显。谢谢!

标签: c linux unix pipe


【解决方案1】:

对于初学者来说,这是错误的。

write(toChild[1], &count, 1) 

它最终会导致您的问题。 countint,而不是 charunsigned char。您需要发送sizeof(count)。此外,遇到 error 时的读取函数将返回 EOF,它是非零的,因此您的子退出条件不合适。它应该看起来像这样:

while(read(toChild[0], &buffer, 1) == 1)

最后,您的父进程应该循环遍历argv[] 中的每个参数,并将每个参数作为strlen 大小的缓冲区发送。

我几乎可以肯定这就是你想要做的。请注意,为了保持清醒地了解哪个描述符用于特定目的,我更喜欢使用#define 来记录每个进程用于读取和写入的内容。这可以扩展到任意数量的进程,顺便说一句,我相信这对于你的下一个任务来说不会太远:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

// P0_READ   - parent read source
// P0_WRITE  - parent write target
// P1_READ   - child read source
// P1_WRITE  - child write target

#define P0_READ     0
#define P1_WRITE    1
#define P1_READ     2
#define P0_WRITE    3
#define N_PIPES     4

int main(int argc, char **argv)
{
    int fd[N_PIPES], count = 0, i;
    pid_t pid;
    char c;

    if (pipe(fd) || pipe(fd+2))
    {
        perror("Failed to open pipe(s)");
        return EXIT_FAILURE;
    }

    // fork child process
    if ((pid = fork()) == -1)
    {
        perror("Failed to fork child process");
        return EXIT_FAILURE;
    }

    // child process
    if (pid == 0)
    {
        // close non P1 descriptors
        close(fd[P0_READ]);
        close(fd[P0_WRITE]);

        // get chars from input pipe, counting each one.
        while(read(fd[P1_READ], &c, 1) == 1)
            count++;

        printf("Child: count = %d\n", count);
        write(fd[P1_WRITE], &count, sizeof(count));

        // close remaining descriptors
        close(fd[P1_READ]);
        close(fd[P1_WRITE]);
        return EXIT_SUCCESS;
    }

    // parent process. start by closing unused descriptors
    close(fd[P1_READ]);
    close(fd[P1_WRITE]);

    // send each arg
    for (i=1; i<argc; ++i)
        write(fd[P0_WRITE], argv[i], strlen(argv[i]));

    // finished sending args
    close(fd[P0_WRITE]);

    // Wait for child process to return.
    wait(NULL);

    // wait for total count
    if (read(fd[P0_READ], &count, sizeof(count)) == sizeof(count))
        printf("Parent: count = %d\n", count);

    // close last descriptor
    close(fd[P0_READ]);

    return 0;
}

输入

./progname argOne argTwo

输出

Child: count = 12
Parent: count = 12

编辑:具有子返回状态的单管道

从原始问题的 cmets 看来,您的作业可能要求获取 child 进程的返回状态作为结果计数,而不是在管道中返回它。在这样做时,您可以使用单个管道描述符对来执行此操作。我更喜欢第一种方法,但这也适用:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

// P0_WRITE  - parent write target
// P1_READ   - child read source

#define P1_READ     0
#define P0_WRITE    1
#define N_PIPES     2

int main(int argc, char **argv)
{
    int fd[N_PIPES], count = 0;
    pid_t pid;
    char c;

    if (pipe(fd))
    {
        perror("Failed to open pipe(s)");
        return EXIT_FAILURE;
    }

    // fork child process
    pid = fork();
    if (pid == -1)
    {
        perror("Failed to fork child process");
        return EXIT_FAILURE;
    }

    if (pid == 0)
    {
        // close non P1 descriptors
        close(fd[P0_WRITE]);

        // Return number of characters counted to parent process.
        while(read(fd[P1_READ], &c, 1) == 1)
            ++count;

        close(fd[P1_READ]);
        printf("Child: count = %d\n", count);
        return count;
    }

    // parent process. start by closing unused descriptors
    close(fd[P1_READ]);

    // eacn each arg entirely
    for (int i=1; i<argc; ++i)
        write(fd[P0_WRITE], argv[i], strlen(argv[i]));

    // finished sending args
    close(fd[P0_WRITE]);

    // Wait for child process to return.
    if (wait(&count) == -1)
    {
        perror("Failed to wait for child process");
        return EXIT_FAILURE;
    }

    printf("Parent: count = %d\n", WEXITSTATUS(count));

    return 0;
}

结果是相同的,但请注意,这是调试的一个问题,因为大多数调试器会在您的子进程上发出信号,并且真正的退出状态会丢失。例如,在我的 Mac 上,在 Xcode trips 下运行:

Failed to wait for child process: Interrupted system call

从命令行运行时给出:

Child: count = 12
Parent: count = 12

我更喜欢两管方法的众多原因之一。

【讨论】:

  • 哇,非常感谢!这正是我需要编程输出的内容。挂起问题一定是我对读/写功能的误解造成的。我真的很感激!
  • @user3698112 很高兴它有帮助。请尝试使用宏或某种形式的符号常量作为管道描述符对/数组的索引,至少在您掌握它们之前。它最终将使它们更容易阅读,更少的编写代码。使用这些符号而不是 [1][3] 等,此答案的发布代码中的意图应该更直观。祝你好运。
  • 我很欣赏关于可读代码的建议。这是我最近一直在努力做的事情,而且大多数时候,我认为我做得很好,但是这个任务给我带来了很多麻烦,以至于我把它拍成了一个几乎没有编辑的问题。无论如何,我真的很感谢你的帮助,我会尽量把你的建议放在心上!
  • @user3698112 还添加了单管道版本,我认为这可能是您的任务目标之一。它有警告,所以请阅读发布的 cmets。很高兴能提供帮助。
  • IN 查看 2 管道示例,我认为它不适用于从子项返回值。因为如果发送端关闭,管道将不起作用,所以 wait(null) 应该在将子值读取到父值之后
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-25
  • 2011-06-17
  • 1970-01-01
  • 2011-08-26
  • 2011-12-11
相关资源
最近更新 更多