如何在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; }
当前代码的问题分析
- 子进程输出缓冲导致延迟:默认情况下,当
std::cout/std::cerr的输出目标不是终端时,会启用全缓冲模式,只有当缓冲区填满或进程终止时才会将数据刷新到管道,导致父进程无法实时获取输出。 - 异步读取方式限制:使用
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
相关产品推荐
相关产品推荐

