使用 libuv 捕获子进程的标准输出

Posted

技术标签:

【中文标题】使用 libuv 捕获子进程的标准输出【英文标题】:Capture a child process's stdout with libuv 【发布时间】:2013-01-22 23:24:38 【问题描述】:

我正在使用 libuv。我已经阅读了http://nikhilm.github.com/uvbook/processes.html,但仍然无法弄清楚如何捕获子进程的标准输出,以便它在父进程中可用(但不能代替父进程的标准输入)。

我的代码目前是:

#include <stdio.h>
#include <stdlib.h>
#include "../../libuv/include/uv.h"

uv_loop_t *loop;
uv_process_t child_req;
uv_process_options_t options;
uv_pipe_t apipe;

void on_child_exit(uv_process_t *req, int exit_status, int term_signal) 
    fprintf(stderr, "Process exited with status %d, signal %d\n", exit_status, term_signal);
    uv_close((uv_handle_t*) req, NULL);


uv_buf_t alloc_buffer(uv_handle_t *handle, size_t len) 
    printf("alloc_buffer called\n");
    uv_buf_t buf;
    buf.base = malloc(len);
    buf.len = len;
    return buf;


void read_apipe(uv_stream_t* stream, ssize_t nread, uv_buf_t buf) 
    printf("read %li bytes from the child process\n", nread);


int main(int argc, char *argv[]) 
    printf("spawn_test\n");
    loop = uv_default_loop();

    char* args[3];
    args[0] = "dummy";
    args[1] = NULL;
    args[2] = NULL;

    uv_pipe_init(loop, &apipe, 0);
    uv_pipe_open(&apipe, 0);

    options.stdio_count = 3;
    uv_stdio_container_t child_stdio[3];
    child_stdio[0].flags = UV_IGNORE;
    child_stdio[1].flags = UV_INHERIT_STREAM;
    child_stdio[1].data.stream = (uv_stream_t *) &apipe;
    child_stdio[2].flags = UV_IGNORE;
    options.stdio = child_stdio;

    options.exit_cb = on_child_exit;
    options.file = args[0];
    options.args = args;

    uv_read_start((uv_stream_t*)&apipe, alloc_buffer, read_apipe);
    if (uv_spawn(loop, &child_req, options)) 
        fprintf(stderr, "%s\n", uv_strerror(uv_last_error(loop)));
        return 1;
    

    return uv_run(loop, UV_RUN_DEFAULT);

dummy.c:

#include <unistd.h>
#include <stdio.h>

int main() 
    printf("child starting\n");
    sleep(1);
    printf("child running\n");
    sleep(2);
    printf("child ending\n");
    return 0;

我有一种唠叨的感觉,我还不太明白 libuv 管道的意义。

【问题讨论】:

【参考方案1】:

我找到了解决办法:

    我打错了标志,它们应该是 UV_CREATE_PIPE | UV_READABLE_PIPE 而不是 UV_INHERIT_STREAM。 我需要在uv_spawn 之后致电uv_read_start。我假设没有数据丢失的可能性,因为 uv_run 还没有被调用。 以上两个修复显示了从dummy 一次到达的所有输出,而不是三个块(就像它在命令行上所做的那样)。 dummy.c 中的 fflush 修复了此问题。

spawn_test:

#include <stdio.h>
#include <stdlib.h>
#include "../../libuv/include/uv.h"

uv_loop_t *loop;
uv_process_t child_req;
uv_process_options_t options;
uv_pipe_t apipe;

void on_child_exit(uv_process_t *req, int exit_status, int term_signal) 
    fprintf(stderr, "Process exited with status %d, signal %d\n", exit_status, term_signal);
    uv_close((uv_handle_t*) req, NULL);


uv_buf_t alloc_buffer(uv_handle_t *handle, size_t len) 
    printf("alloc_buffer called, requesting a %lu byte buffer\n");
    uv_buf_t buf;
    buf.base = malloc(len);
    buf.len = len;
    return buf;


void read_apipe(uv_stream_t* stream, ssize_t nread, uv_buf_t buf) 
    printf("read %li bytes in a %lu byte buffer\n", nread, buf.len);
    if (nread + 1 > buf.len) return;
    buf.base[nread] = '\0'; // turn it into a cstring
    printf("read: |%s|", buf.base);


int main(int argc, char *argv[]) 
    printf("spawn_test\n");
    loop = uv_default_loop();

    char* args[3];
    args[0] = "dummy";
    args[1] = NULL;
    args[2] = NULL;

    uv_pipe_init(loop, &apipe, 0);
    uv_pipe_open(&apipe, 0);

    options.stdio_count = 3;
    uv_stdio_container_t child_stdio[3];
    child_stdio[0].flags = UV_IGNORE;
    child_stdio[1].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
    child_stdio[1].data.stream = (uv_stream_t *) &apipe;
    child_stdio[2].flags = UV_IGNORE;
    options.stdio = child_stdio;

    options.exit_cb = on_child_exit;
    options.file = args[0];
    options.args = args;

    if (uv_spawn(loop, &child_req, options)) 
        fprintf(stderr, "%s\n", uv_strerror(uv_last_error(loop)));
        return 1;
    
    uv_read_start((uv_stream_t*)&apipe, alloc_buffer, read_apipe);

    return uv_run(loop, UV_RUN_DEFAULT);

dummy.c:

#include <unistd.h>
#include <stdio.h>

int main() 
    printf("child starting\n");
    fflush(stdout);
    sleep(1);
    printf("child running\n");
    fflush(stdout);
    sleep(2);
    printf("child ending\n");
    fflush(stdout);
    return 0;

【讨论】:

【参考方案2】:

在 libuv 单元测试libuv/test/test-stdio-over-pipes.c 中查看他们是如何做到的:

请勿拨打uv_pipe_open 儿童标准输入的标志:UV_CREATE_PIPE | UV_READABLE_PIPE 孩子的标准输出和标准错误的标志:UV_CREATE_PIPE | UV_WRITABLE_PIPE

Windows 上还有一个issue,其中uv_spawn 可能会在遇到错误时返回零,在这种情况下,您需要检查process.spawn_error,它只存在于Windows 上。

【讨论】:

以上是关于使用 libuv 捕获子进程的标准输出的主要内容,如果未能解决你的问题,请参考以下文章

Python 子进程丢失了程序标准输出的 10%

从子进程中实时捕获标准输出

将生成的进程标准输出捕获为 unicode

使用文件作为子进程的标准输入和标准输出

使用 BOOST 进程在单独的线程中读取子进程标准输出

重定向子进程标准输出