【问题标题】:stdout of spawned process works strange when being piped产生的进程的标准输出在被管道传输时工作起来很奇怪
【发布时间】:2016-09-11 00:26:15
【问题描述】:

我的目标是将某些流程执行的会话保存到 json(类似[{ type: 'stdout', data: 'What's your name?' }, { type: 'stdin', data: 'Alex.' }, { type: 'stdout', data: 'Hi, Alex!' }])。我决定用 nodejs 来做,但我遇到了一些问题。生成进程的标准输出在通过管道传输和继承时的工作方式不同。所以,例如我有这个 C 代码(简单的猜数游戏)编译为main

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
  int random_num = 0;
  int guessed_num = 0;
  int counter = 0;

  srand(time(NULL));
  random_num = rand() % 10 + 1;

  printf("Guess my number! ");

  while(1) {
    counter++;

    scanf("%d", &guessed_num);

    if (guessed_num == random_num) {
      printf("You guessed correctly in %d tries! Congratulations!\n", counter);
      break;
    }

    if (guessed_num < random_num)
      printf("Your guess is too low. Guess again. ");

    if (guessed_num > random_num)
      printf("Your guess is too high. Guess again. ");
  }

  return 0;
}

还有这段 JavaScript 代码:

var spawn = require('child_process').spawn;
var child = spawn('./main', { stdio: 'inherited' });

这段 JavaScript 代码的执行结果是:

Guess my number! 1
Your guess is too low. Guess again. 2
Your guess is too low. Guess again. 3
You guessed correctly in 3 tries! Congratulations!

但是,当stdioinherited 时,我无法附加到任何流程流并将数据保存到 json。所以我尝试了这个:

var spawn = require('child_process').spawn;
var child = spawn('./main', { stdio: 'pipe' });
child.stdout.on('data', function(data) { process.stdout.write(data) });
process.stdin.on('data', function(data) { child.stdin.write(data) });

并作为执行的结果得到这个:

1
2
3
Guess my number! Your guess is too low. Guess again. Your guess is too low. Guess again. You guessed correctly in 3 tries! Congratulations!

当子进程等待输入时,子进程的标准输出以某种方式挂起。可能它与事件循环有关,但我不确定。反正inheritedpipe的行为是很不一样的,好像是不对的……

我能做些什么呢?也许有一些解决方法?

【问题讨论】:

    标签: javascript c node.js unix


    【解决方案1】:

    inherited 模式下,C 程序继承绑定到终端的描述符。终端是交互式设备,它会自动将printf() 函数的缓冲设置为stdout 进行行缓冲(即遇到新行时刷新缓冲区)。

    OTOH,在pipe 模式下,libc 未检测到交互式设备,并切换到全缓冲(即,仅当缓冲区已满或调用 fflush() 时才刷新缓冲区。

    一种解决方法是在每次调用printf() 之后调用fflush(stdout),或者禁用stdout 的缓冲:setvbuf(stdout, NULL, _IONBF, 0);

    请参阅此answer 以获取有关管道与终端缓冲的完整说明,包括 libc 确定 stdout 的缓冲的示例。

    【讨论】:

    • 谢谢,它解释了很多。我在 Node 文档中没有注意这件事:“但是请注意,有些程序在内部使用行缓冲 I/O。虽然这不会影响 Node.js,但这可能意味着发送到子进程的数据可能不能立即食用。”我看到的另一个选择是使用 PTY(例如pty.js),但在这种情况下,将失去区分 stdout 和 stderr 的能力。因此,模拟交互式设备并区分 stdout 和 stderr 流似乎是不可能的。
    • 但是,使用 (dockerode)[github.com/apocas/dockerode] 在 Docker 容器中运行相同的 C 程序实际上可以解复用流。我已经阅读了它的源代码,它似乎连接到/var/run/docker.sock 套接字,然后解复用 Docker 导出的流。所以似乎 Docker 以某种方式处理它......我只是不明白如何:)
    • 哇,我找到了一个更简单的解决方案。 spawn('stdbuf', ['-i0', '-o0', '-e0', './main'], { stdio: 'pipe' }) 工作得很好!更改 C 代码很好,但我并不总是有它的来源,所以我认为这个解决方案更合适。无论如何,非常感谢!
    猜你喜欢
    • 2018-02-14
    • 1970-01-01
    • 2013-06-25
    • 2019-03-17
    • 2020-05-14
    • 2018-09-12
    • 1970-01-01
    • 2021-07-26
    • 1970-01-01
    相关资源
    最近更新 更多