了解管道、重定向和 IPC

Understanding Pipes, Redirection and IPC

我是管道新手,一直在尝试创建一对管道,允许 child 进程写入 parent 进程,而 parent 进程写入沟通回来。有 1 parent 最多 4 children。 child 与 exec.

成为不同的程序

我的工作:

正在从 parent 写入到 child 进程。当我读入 child 程序的标准输入时,它将收到我从 parent.

中写入的内容

目标:

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

我坚持的是:

我不确定如何获取它以便 parent 可以通过文件流读取 child 的标准输出。当我尝试设置 child 行的读数时,代码似乎停止工作。甚至 child 也不能从 parent 中读取(它似乎停止在现在注释掉的用于设置 child 到 parent 的留置权)。

我也不确定如何 "wait" 直到出现一些东西。比如,一开始玩家必须向 parent 发送一条 "ready" 消息,让他们知道他们正在工作。我从 child 发送 "ready" 后,如何无限期地 "wait" 直到下一条消息出现?

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

为了让 parent 写入 child,我收集到的是:

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

这是正确的吗?我尝试将 child 的这种逻辑应用到 parent 但将其反转。

  1. 将输入管道连接到读取文件流,这是 child 程序从其标准输出写入的地方。
  2. 将输出管道连接到读取流,parent 从中读取。

    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");
          }
    
      }
     }
    

我的 child 主要内容如下:

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

但我不确定

此外,我不允许使用 popen() 或 write()。我也被鼓励使用文件流 apparently.

我主要针对您在 parent 和 child 进程之间建立 two-way 通信的主要问题。如果您想要其他答案,请提出单独的问题。

你似乎有一个合理的通用方法,但你确实有一个严重的误解/设计缺陷:而多个客户端将它们的标准流连接到管道以与 parent 进程通信是合理的,如果您希望一次能够处理多个客户端,则不能将所有这些管道的 parent 端连接到 parent 的标准流。毕竟 parent 只有一套标准流。那么,为了支持多个客户端,parent 进程必须为每个客户端维护一对单独的文件描述符 and/or 流,并且必须通过这些而不是通过其标准流进行通信。

我不确定为什么当您连接 child-to-parent 方向时您的 parent / child 通信失败。该过程确实类似于设置另一个端点。这是一个工作示例:


parent.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;
}

与您的代码对比,请注意

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

另请注意,对于等待 "appear" 的操作,您以这种方式设置的 IPC 流上的 I/O 操作将自动产生该效果。但是,您如何能够或应该如何利用它肯定是一个不同的问题。