【问题标题】:Understanding Pipes, Redirection and IPC了解管道、重定向和 IPC
【发布时间】:2015-12-25 17:33:34
【问题描述】:

我是管道新手,我一直在尝试创建一对管道,允许子进程写入父进程,并让父进程返回通信。有 1 位家长,最多 4 位孩子。子进程通过 exec 成为不同的程序。

我的工作:

从父进程写入子进程。当我读入子程序的标准输入时,它会收到我从父程序写的内容。

目标:

创建一个纸牌游戏,其中父母与每个单独的客户(子进程)交谈,并向他们提供从标准输出到子标准输入的所有动作和信息。各个子进程在其标准输出上返回他们的动作,由主要父进程读取。游戏的动作完全由顺序决定,而不是玩家。所以这是一个机器人游戏。

我的困惑:

我不确定如何获取它,以便父母可以通过文件流读取孩子的标准输出。当我尝试设置从子行读取时,代码似乎停止工作。甚至孩子也不能从父母那里阅读(它似乎停在现在注释掉的用于将孩子设置为父母的留置权上)。

我也不确定如何“等待”直到出现某些东西。就像,一开始玩家必须向父母发送一条“准备就绪”的消息,让他们知道他们正在工作。一旦我从孩子那里发送“就绪”,我如何无限期地“等待”直到下一条消息出现?

我不确定我是否正确设置了管道。有人可以提供有关如何使用通信管道的指导并在下面确认我的逻辑吗?

我收集的让父母写信给孩子的是:

  1. 先创建管道
  2. 将父进程分叉到另一个进程(子进程)
  3. 将管道的输入连接到父级的标准输出,并使用 dup2 关闭父级的读取端并关闭
  4. 将管道输出连接到孩子的标准输入,并使用 dup2 关闭孩子的书写部分并关闭
  5. 使用 fdopen() 从文件描述符中获取文件流,然后打印到该文件流。
  6. 子进程标准输入现在是您从父进程打印到标准输出的任何内容。

这是正确的吗?我尝试将这种针对孩子的逻辑应用于父母,但将其反转。

  1. 将输入管道连接到读取文件流,这是子程序从其标准输出写入的位置。
  2. 将输出管道连接到父级读取的读取流。

    void start_child_process(Game *game, int position) {
      int child2Parent[2];
      int parent2Child[2];
    
      if (pipe(parent2Child)) {
          printf("PIPE FAIL!\n");
      }
      if (pipe(child2Parent)) {
          printf("PIPE FAIL!\n");
      }
    
      pid_t pid = fork();
      game->readStream[position] = fdopen(child2Parent[0], "r");
      game->writeStream[position] = fdopen(parent2Child[1], "w");
    
      if (pid) { // Parent
          // Write from parent to child
          close(parent2Child[0]);
          dup2(fileno(game->writeStream[position]), STDOUT_FILENO);
          fprintf(game->writeStream[position], "%s", "test message");
          fflush(game->writeStream[position]);
          close(parent2Child[1]);
    
          // Read from child -- not working
    
          /*dup2(child2Parent[0], STDIN_FILENO);
          close(child2Parent[0]);
          close(child2Parent[1]);
          */
    
      } else {
          // Setup child to read from stdin from parent
          dup2(parent2Child[0], STDIN_FILENO);
          close(parent2Child[1]);
    
          // Setup writing from child to parent
    
          /*
          if (dup2(child2Parent[1], STDOUT_FILENO) == -1) {
              fprintf(stderr, "dup2 in child failed\n");
          } else {
              fprintf(stderr, "dup2 in child successful\n");
              close(child2Parent[0]);
              close(child2Parent[1]);
          }
          */
    
    
          if ((int)execl("child", "2", "A", NULL) == -1) {
              printf("Failed child process\n");
          }
    
      }
     }
    

我的孩子主要有以下内容:

char string[100];
printf("reading from pipe: %s\n", fgets(string, 100, stdin));

但我不确定如何

另外,我不允许使用 popen() 或 write()。显然,我也被鼓励使用文件流。

【问题讨论】:

    标签: c linux pipe


    【解决方案1】:

    我主要谈谈您在父进程和子进程之间建立双向通信的主要问题。如果您想要其他答案,请提出单独的问题。

    您似乎有一个合理的通用方法,但您确实有一个严重的误解/设计缺陷:虽然多个客户端将其标准流连接到管道以与父进程通信是合理的,但您无法连接父进程的如果您希望一次能够处理多个客户端,请结束所有这些管道到父级标准流。毕竟,父级只有一组标准流。因此,为了支持多个客户端,父进程必须为每个客户端维护一对单独的文件描述符和/或流,并且必须通过这些而不是通过其标准流进行通信。

    我不确定为什么当您连接孩子对父母的方向时您的父母/孩子沟通会失败。该过程确实类似于设置另一个端点。这是一个工作示例:


    父.c:

    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <unistd.h>
    
    int main() {
      int child2Parent[2];
      int parent2Child[2];
      char buffer[256];
      FILE *p2cStream;
      FILE *c2pStream;
      pid_t pid;
    
      if (pipe(parent2Child) || pipe(child2Parent)) {
          perror("Failed to create pipes");
          exit(EXIT_FAILURE);
      }
    
      switch (pid = fork()) {
        case -1: /* error */
          perror("Failed to fork");
          break;
        case 0:  /* child */
          // Setup child to read from stdin from parent
          close(parent2Child[1]);  /* ignoring any error */
          close(child2Parent[0]);  /* ignoring any error */
          if ((dup2(parent2Child[0], STDIN_FILENO) < 0)
              || (dup2(child2Parent[1], STDOUT_FILENO) < 0)) {
            perror("Failed to duplicate file descriptors");
          } else {
            /* conventionally, the first program argument is the program name */
            /* also, execl() returns only if it fails */
            execl("child", "child", "2", "A", NULL);
            perror("Failed to exec child process");
          }
    
          exit(EXIT_FAILURE);
          break;
        default: /* parent */
          close(parent2Child[0]);  /* ignoring any error */
          close(child2Parent[1]);  /* ignoring any error */
          if (!(p2cStream = fdopen(parent2Child[1], "w"))
              || !(c2pStream = fdopen(child2Parent[0], "r"))) {
            perror("Failed to open streams");
            exit(EXIT_FAILURE);
          }
          if ((fprintf(p2cStream, "test message from parent\n") < 0) 
              || fclose(p2cStream)) {
            perror("Failed to write to the child");
            exit(EXIT_FAILURE);
          }
          if (fscanf(c2pStream, "%255[^\n]", buffer) < 1) {
            perror("Failed to read the child's message");
            exit(EXIT_FAILURE);
          }
          printf("The child responds: '%s'\n", buffer); /* ignoring any error */
          break;
      }
    
      return EXIT_SUCCESS;
    }
    

    child.c:

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <errno.h>
    
    int main(int argc, char *argv[]) {
        char buffer[256] = { 0 };
    
        if (scanf("%255[^\n]", buffer) < 0) {
            perror("Failed to reading input");
            exit(EXIT_FAILURE);
        }
    
        /*
         * If stdout is connected to the parent then we must avoid
         * writing anything unexpected to it
         */
        if (fprintf(stderr, "received: '%s'\n", buffer) < 0) {
            perror("Failed to echo input");
            exit(EXIT_FAILURE);
        }
    
        printf("Hi, Mom!\n");  /* ignoring any error */
        fflush(stdout);        /* ignoring any error */
    
        return EXIT_SUCCESS;
    }
    

    与您的代码相比,请注意

    • 注意检查所有可能表示我关心的错误的返回值;
    • 父母使用标准流以外的流与孩子进行通信(尽管只有一个孩子,这是一种方便而非必要);
    • execl() 参数的约定。

    另请注意,对于等待某物“出现”,您以这种方式设置的 IPC 流上的 I/O 操作将自动产生该效果。但是,您可以或应该如何利用它当然是一个不同的问题。

    【讨论】:

    • 您好,感谢您的帮助。所以我对你的代码做了一些修改。孩子:pastebin.com/6iq2n1JU,父母:pastebin.com/UPbbG6qg。但是我得到的输出是:pastebin.com/g50gAeW1 这是基本上在父母和孩子之间阅读的正确方法吗?就像父母发送“嗨”一样,等待孩子说“嗨妈妈”,然后说“你好吗?”回给孩子,等待回复?
    • 如果您对我展示的代码如何工作或为什么按原样编写代码有任何疑问,请随时提问。
    • 我在写完之前不小心发表了评论,对不起。您是否应该在每次写入后关闭文件流(p2cStream)?我可以用这两行把它变成一个函数,比如 write_to_child(char* message) 吗?
    • @user3139573,您可以根据需要保持流打开,并通过它们交换许多消息,尽管一旦孩子退出它们将不再有用。如果您关闭它们,您将无法将它们替换为连接到同一个孩子的新的。您应该在不再需要流时关闭它们,至少在您准备好让父级退出之前关闭它们。
    猜你喜欢
    • 1970-01-01
    • 2012-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-04
    • 1970-01-01
    相关资源
    最近更新 更多