You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C++程序中实时获取子进程的stdout与stderr输出?

问题:Boost.Process实时捕获子进程stdout/stderr并合并输出时序

问题描述

在Ubuntu 22.04使用Boost.Process 1.81.0开发跨平台自定义SSH功能时,启动子进程后无法实时捕获其stdout和stderr输出,仅能在子进程终止时获取全部输出。同时希望合并stdout与stderr并保留输出的时序顺序。

相关代码

process_control.cpp

#include <boost/process.hpp>
#include <boost/process/pipe.hpp>
#include <boost/asio/io_service.hpp>

#include <thread>
#include <iostream>


int main() {
    using namespace boost;

    std::string output{};
    std::string error{};

    asio::io_service ios;

    std::vector<char> vOut(128 << 10);
    auto outBuffer{asio::buffer(vOut)};
    process::async_pipe pipeOut(ios);

    std::function<void(const system::error_code &ec, std::size_t n)> onStdOut;
    onStdOut = [&](const system::error_code &ec, size_t n) {
        std::cout << "onSTDOUT CALLED.\n";
        output.reserve(output.size() + n);
        output.insert(output.end(), vOut.begin(), vOut.begin() + static_cast<long>(n));
        if (!ec) {
            asio::async_read(pipeOut, outBuffer, onStdOut);
        } else {
            std::cout << "STDOUT ERROR\n";
        }
        std::cout << output << "\n";
    };

    std::vector<char> vErr(128 << 10);
    auto errBuffer{asio::buffer(vErr)};
    process::async_pipe pipeErr(ios);
    std::function<void(const system::error_code &ec, std::size_t n)> onStdErr;
    onStdErr = [&](const system::error_code &ec, size_t n) {
        std::cout << "onSTDERR CALLED.\n";
        error.reserve(error.size() + n);
        error.insert(error.end(), vErr.begin(), vErr.begin() + static_cast<long>(n));
        if (!ec) {
            asio::async_read(pipeErr, errBuffer, onStdErr);
        } else {
            std::cout << "STDERR ERROR\n";
        }
        std::cout << error << "\n";
    };

    process::opstream in;
    process::child c(
            "test_program",
            process::std_out > pipeOut,
            process::std_err > pipeErr,
            process::std_in < in,
            ios
    );

    asio::async_read(pipeOut, outBuffer, onStdOut);
    asio::async_read(pipeErr, errBuffer, onStdErr);
    std::jthread t{[&ios] { ios.run(); }};

    std::cout<<"STARTING LOOP: \n";
    do {
        std::string input_command{};
        std::cout << "ENTER INPUT: ";
        std::getline(std::cin, input_command);
        if (c.running()) { //to prevent sigpipe if process dies during input
            in << input_command << std::endl;
        }
        std::this_thread::yield();
    } while (c.running());
    return 0;
}

test_program.cpp

#include <iostream>
#include <chrono>
#include <thread>


using namespace std::chrono_literals;

int main() {
    std::cout<<"Started program.\n";
    while(true){
        std::cout<<"Something\n";
        std::cerr<<"error stream\n";
        std::this_thread::sleep_for(0.5s);
        if(std::rand()%3==0){
            std::cout<<"Waiting for input...\n";
            std::string input{};
            std::getline(std::cin, input);
            std::cout<<"Got input: \""<<input<<"\"\n";
            if(input=="end"){
                break;
            }
        }
    }
    return 0;
}

当前代码的问题分析

  1. 子进程输出缓冲导致延迟:默认情况下,当std::cout/std::cerr的输出目标不是终端时,会启用全缓冲模式,只有当缓冲区填满或进程终止时才会将数据刷新到管道,导致父进程无法实时获取输出。
  2. 异步读取方式限制:使用asio::async_read要求读取满整个指定缓冲区(此处为128KB)才触发回调,而非有数据就立即读取,进一步加剧了输出延迟。

解决方案

1. 实现实时输出捕获

方案A:修改子进程强制刷新输出

在子进程的每次输出后手动调用std::flush,或替换\n为std::endl(endl会自动触发换行+刷新)。修改test_program.cpp的输出逻辑:

std::cout<<"Started program.\n"<<std::flush;
while(true){
    std::cout<<"Something\n"<<std::flush;
    std::cerr<<"error stream\n"<<std::flush;
    // ... 其余代码不变
}

或者直接关闭子进程的输出缓冲,在main开头添加:

std::cout.setbuf(nullptr, 0); // 关闭stdout缓冲
std::cerr.setbuf(nullptr, 0); // 关闭stderr缓冲

方案B:替换异步读取方式

将asio::async_read改为asio::async_read_some,该函数会在有可用数据时立即读取并触发回调,无需等待填满缓冲区。修改process_control.cpp中的异步读取代码:

// 替换原有的async_read调用
asio::async_read_some(pipeOut, outBuffer, onStdOut);
asio::async_read_some(pipeErr, errBuffer, onStdErr);

// 回调函数内的后续读取也替换为async_read_some
onStdOut = [&](const system::error_code &ec, size_t n) {
    // ... 原有逻辑不变
    if (!ec) {
        asio::async_read_some(pipeOut, outBuffer, onStdOut);
    }
    // ...
};

onStdErr = [&](const system::error_code &ec, size_t n) {
    // ... 原有逻辑不变
    if (!ec) {
        asio::async_read_some(pipeErr, errBuffer, onStdErr);
    }
    // ...
};

2. 合并stdout与stderr并保留时序

直接使用Boost.Process内置的merge_out_err操作符,将stderr的输出合并到stdout管道中,这样父进程只需处理一个管道,即可保证输出时序与子进程实际输出顺序一致。修改process_control.cpp的子进程启动代码:

// 移除pipeErr相关的所有代码,仅保留pipeOut
process::child c(
        "test_program",
        process::std_out > pipeOut,
        process::std_err > process::merge_out, // 将stderr合并到stdout
        process::std_in < in,
        ios
);

此时只需维护一套stdout的异步读取逻辑即可,无需单独处理stderr。

补充方案的优缺点分析

你提到的通过共享文件实现输出捕获的方案,优点是实现简单,但存在明显不足:

  • 依赖文件IO,性能远低于管道通信;
  • 跨平台下文件读写的同步逻辑复杂(如Windows需设置文件共享模式);
  • 每次重新打开文件读取更新内容的效率低下,不适合实时性要求高的场景。因此该方案并非最优选择。

内容的提问来源于stack exchange,提问作者Erionn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 23:08:14