【问题标题】:using pipes between parent and child processes在父进程和子进程之间使用管道
【发布时间】:2019-02-27 20:59:22
【问题描述】:

我试图从一个 C 程序中派生出一个子进程来运行一个 python 脚本,该脚本应该在它的标准输入上处理来自父级的行并将结果返回到标准输出上的父级,即。父级将提供输入行并从子级标准输出读取结果。

只有父母和孩子都只是停留在从彼此那里读取第一行数据。

想知道为什么,感谢任何提示,TIA!

这是一个示例运行,从 C-parent 开始:

# ./pycalc
dude
Got this to do calc with: dude
^CTraceback (most recent call last):
  File "./pycalc.py", line 9, in <module>
    line=raw_input()
KeyboardInterrupt

这是示例 python 脚本:

#!/usr/bin/python

import base64
import os

line=''
while True:
  try:
    line=raw_input()
  except EOFError:
    break

  print(base64.encodestring(line))

print('Bye - End of Calc!')

这是父 C 源代码:

#define PARENT_READ read_pipe[0]
#define PARENT_WRITE write_pipe[1]
#define CHILD_WRITE read_pipe[1]
#define CHILD_READ  write_pipe[0]


int main(int argc, char *argv[])
{
  int            pid;
  int            read_pipe[2];
  int            write_pipe[2];
  FILE          *wrp = (FILE *)NULL;
  FILE          *rdp = (FILE *)NULL;

  char           line[1024+1];

  if ( pipe(read_pipe) < 0 ) exit(-1);
  if ( pipe(write_pipe) < 0 ) exit(-1);

  if ( (pid=fork()) < 0 )
    exit(-1);
  else if (pid == 0) {
    // Child process go here
    close(PARENT_READ);
    close(PARENT_WRITE);

    dup2(CHILD_READ, STDIN_FILENO);
    dup2(CHILD_WRITE, STDOUT_FILENO);
    close(CHILD_READ);
    close(CHILD_WRITE);
    setlinebuf(stdin);
    setlinebuf(stdout);
    const char *argv[] = {"pycalc.py",NULL};
    execvp("pycalc.py", (char **)argv);
    exit(errno);
  }

  // Parent process go here
  close(CHILD_READ);
  close(CHILD_WRITE);
  if ( (wrp = fdopen(PARENT_WRITE, "w")) == NULL ) exit(-1);
  setlinebuf(wrp);
  if ( (rdp = fdopen(PARENT_READ, "r")) == NULL ) exit(-1);
  setlinebuf(rdp);

  while ( fgets(line, sizeof(line), stdin)!=NULL ) {
    printf("Got this to do calc with: %s", line);
    fprintf(wrp,"%s", line);
    fflush(wrp);
    fgets(line,sizeof(line),rdp);
    printf("Got this calc from child: %s", line);
  }

  exit(0);
}

【问题讨论】:

  • 顺便说一下,应该是exit(EXIT_FAILURE) 而不是exit(-1)
  • 使用fork而不是popen的任何具体原因?
  • 看起来父母和孩子都尝试先阅读,以阻止方式。这自然会陷入僵局。没有?
  • setlinebuf 在子级之前execvp 无效。
  • @hellow : 你只能用popen()做一个方向的I/O。这需要双向通信。

标签: python c python-2.7


【解决方案1】:

我已经对自己设计的一些命令行工具进行了足够多的实验,让我相信问题在于 Python 在这种管道上下文中的工作方式。它在获得 EOF 之前不会处理数据(因为提供的数据不足以填充缓冲区)。因此,尽管raw_input 尽了最大努力并尝试在 Python 执行之前设置行缓冲,但它不会在行可用时立即读取数据。我的测试管道连续有 6 个进程:

timecmd -m -- cat -u | tstamp -f3 | tee /dev/tty | timecmd -m -- pycalc.py |
tstamp -f3 | tee $(isodate -c).log 

timecmd 命令报告命令的开始时间,运行命令,报告退出时间(-m 为毫秒时间)。 tstamp -f3 在每行输出前加上时间戳(-f3 表示毫秒时间)。使用cat -u 进行无缓冲输出;它报告可用的线路; tee /dev/tty 在终端上显示带时间戳的输出并将其提供给pycalc.py;输出再次带有时间戳,然后通过管道传输到tee 以进行日志记录(与isodate -c 的组合会生成一个日志文件名,例如20180924.072626.log)。当我使用它时,我清楚地看到 pycalc.py 在我向 cat -u 指示 EOF 之前不会做任何事情。

$ timecmd -m -- cat -u | tstamp -f3 | tee /dev/tty | timecmd -m -- pycalc.py |
> tstamp -f3 | tee $(isodate -c).log
2018-09-24 07:29:06.419 [PID 44960] cat -u
2018-09-24 07:29:06.419 [PID 44961] pycalc.py
dude
2018-09-24 07:29:09.274: dude
how
2018-09-24 07:29:10.819: how
are
2018-09-24 07:29:12.349: are
you
2018-09-24 07:29:16.540: you
2018-09-24 07:29:26.205 [PID 44960; status 0x0000]  -  19.786s
2018-09-24 07:29:26.209 [PID 44961; status 0x0000]  -  19.789s
2018-09-24 07:29:26.208: MjAxOC0wOS0yNCAwNzoyOTowOS4yNzQ6IGR1ZGU=
2018-09-24 07:29:26.209: 
2018-09-24 07:29:26.209: MjAxOC0wOS0yNCAwNzoyOToxMC44MTk6IGhvdw==
2018-09-24 07:29:26.209: 
2018-09-24 07:29:26.209: MjAxOC0wOS0yNCAwNzoyOToxMi4zNDk6IGFyZQ==
2018-09-24 07:29:26.209: 
2018-09-24 07:29:26.209: MjAxOC0wOS0yNCAwNzoyOToxNi41NDA6IHlvdQ==
2018-09-24 07:29:26.209: 
2018-09-24 07:29:26.209: Bye - End of Calc!
$

所以,AFAICT,当标准输入来自管道时,您需要找到一种方法让 Python 从标准输入中读取行,并在缓冲区已满之前读取它们。在那之前,你不会成功。在确保正确(及时)刷新输出时,您可能也会遇到类似的问题。您可以通过对 Python 看到的输入加时间戳来进一步完善我所做的测试,也许将加时间戳的数据报告给标准错误(这样您就知道 Python 在做什么),但我有一定的把握,我的设置及时将数据发送到 Python,但 Python没有认识到这一点。

在“python 行缓冲标准输入”上使用 Google 搜索会出现 Setting smaller buffer size for sys.stdin,这表明使用 python -u。当我在长长的命令行中使用timecmd -m -- python -u pycalc.py 时,我得到了预期的输出——Python 在生成数据时做出响应。

$ timecmd -m -- cat -u | tstamp -f3 | tee /dev/tty | timecmd -m -- python -u pycalc.py |
> tstamp -f3 | tee $(isodate -c).log
2018-09-24 07:52:41.485 [PID 45180] cat -u
2018-09-24 07:52:41.485 [PID 45181] python -u pycalc.py
dude
2018-09-24 07:52:43.213: dude
2018-09-24 07:52:43.214: MjAxOC0wOS0yNCAwNzo1Mjo0My4yMTM6IGR1ZGU=
2018-09-24 07:52:43.215: 
how
2018-09-24 07:52:48.852: how
2018-09-24 07:52:48.852: MjAxOC0wOS0yNCAwNzo1Mjo0OC44NTI6IGhvdw==
2018-09-24 07:52:48.852: 
are
2018-09-24 07:52:50.720: are
2018-09-24 07:52:50.720: MjAxOC0wOS0yNCAwNzo1Mjo1MC43MjA6IGFyZQ==
2018-09-24 07:52:50.720: 
you
2018-09-24 07:52:52.479: you
2018-09-24 07:52:52.479: MjAxOC0wOS0yNCAwNzo1Mjo1Mi40Nzk6IHlvdQ==
2018-09-24 07:52:52.479: 
2018-09-24 07:52:53.646 [PID 45180; status 0x0000]  -  12.161s
2018-09-24 07:52:53.647: Bye - End of Calc!
2018-09-24 07:52:53.650 [PID 45181; status 0x0000]  -  12.165s
$

您可以从时间戳中看到当打字(故意慢打字)发生时这是如何反应的。将其转换为您的程序需要做的事情,我想出:

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

#define PARENT_READ read_pipe[0]
#define PARENT_WRITE write_pipe[1]
#define CHILD_WRITE read_pipe[1]
#define CHILD_READ  write_pipe[0]

int main(void)
{
    int pid;
    int read_pipe[2];
    int write_pipe[2];

    if (pipe(read_pipe) < 0)
        exit(-1);
    if (pipe(write_pipe) < 0)
        exit(-1);

    if ((pid = fork()) < 0)
        exit(-1);
    else if (pid == 0)
    {
        // Child process go here
        close(PARENT_READ);
        close(PARENT_WRITE);

        dup2(CHILD_READ, STDIN_FILENO);
        dup2(CHILD_WRITE, STDOUT_FILENO);
        close(CHILD_READ);
        close(CHILD_WRITE);
        char *argv[] = {"python", "-u", "pycalc.py", NULL};
        execvp(argv[0], argv);
        fprintf(stderr, "failed to execute %s (%d: %s)\n", argv[0], errno, strerror(errno));
        exit(errno);
    }

    // Parent process go here
    close(CHILD_READ);
    close(CHILD_WRITE);
    FILE *wrp = fdopen(PARENT_WRITE, "w");
    FILE *rdp = fdopen(PARENT_READ, "r");
    if (wrp == NULL || rdp == NULL)
        exit(-1);
    setlinebuf(wrp);
    setlinebuf(rdp);

    char line[1024 + 1];
    while (fgets(line, sizeof(line), stdin) != NULL)
    {
        printf("Got this to do calc with: %s", line);
        fprintf(wrp, "%s", line);
        fflush(wrp);
        fgets(line, sizeof(line), rdp);
        printf("Got this calc from child: %s", line);
    }

    return(0);
}

当运行时(程序是pipe43),我得到:

$ pipe43
dude
Got this to do calc with: dude
Got this calc from child: ZHVkZQ==
how
Got this to do calc with: how
Got this calc from child: 
are   
Got this to do calc with: are
Got this calc from child: aG93
you
Got this to do calc with: you
Got this calc from child: 
Traceback (most recent call last):
  File "pycalc.py", line 15, in <module>
    print('Bye - End of Calc!')
IOError: [Errno 32] Broken pipe
$

Python 的错误是因为它在父进程关闭其管道后尝试写入 - 它不会等待来自 Python 的杂散输出。

我不确定-u 选项对 Python 的效率影响;我怀疑他们不是很好。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-07
    • 2012-03-04
    • 2019-09-02
    • 1970-01-01
    • 2015-09-27
    相关资源
    最近更新 更多