使用Windows API,我们可以使用管道转发进程输出和错误流,因此我们可以读取进程输出而无需任何临时文件。代替这个:
std::system("my_command.exe > out.tmp");
我们可以更快地工作,并且没有风险来生成很多被遗忘的临时文件(例如在系统崩溃时)。
Linux具有类似的功能。但是,为每个操作系统实现特定于操作系统的代码是耗时且复杂的任务,因此使用一些便携式解决方案似乎是个好主意。
boost::process
声称是这样的解决方案。但是,从根本上讲,这是不可靠的。请参阅以下示例程序:
#include <fstream>
#include <iostream>
#include <memory>
#include <vector>
#include <boost/asio.hpp>
#include <boost/process.hpp>
void ReadPipe(boost::process::async_pipe& pipe, char* output_buffer, size_t output_buffer_size, boost::asio::io_context::strand& executor, std::ofstream& output_saver)
{
namespace io = boost::asio;
using namespace std;
io::async_read(
pipe,
io::buffer(
output_buffer,
output_buffer_size
),
io::bind_executor(executor, [&pipe, output_buffer, output_buffer_size, &executor, &output_saver](const boost::system::error_code& error, std::size_t bytes_transferred) mutable
{
// Save transferred data
if (bytes_transferred)
output_saver.write(output_buffer, bytes_transferred);
// Handle error
if (error)
{
if (error.value() == boost::asio::error::basic_errors::broken_pipe)
cout << "Child standard output is broken, so the process is most probably exited." << endl;
else
cout << "Child standard output read error occurred. " << boost::system::system_error(error).what() << endl;
}
else
{
//this_thread::sleep_for(chrono::milliseconds(50));
ReadPipe(pipe, output_buffer, output_buffer_size, executor, output_saver);
}
})
);
}
int main(void)
{
namespace io = boost::asio;
namespace bp = boost::process;
using namespace std;
// Initialize
io::io_context asio_context;
io::io_context::strand executor(asio_context);
bp::async_pipe process_out(asio_context);
char buffer[65535];
constexpr const size_t buffer_size = sizeof(buffer);
ofstream output_saver(R"__(c:\screen.png)__", ios_base::out | ios_base::binary | ios_base::trunc);
// Schedule to read standard output
ReadPipe(process_out, buffer, buffer_size, executor, output_saver);
// Run child
bp::child process(
bp::search_path("adb"),
bp::args({ "exec-out", "screencap", "-p" }),
bp::std_in.close(),
bp::std_out > process_out,
bp::std_err > process_out
);
asio_context.run();
process.wait();
output_saver.close();
// Finish
return 0;
}
这段代码很好用;它运行ADB,生成Android设备屏幕截图,并通过异步管道进行保存,因此不涉及任何临时文件。这个特定的示例将屏幕截图保存为文件,但是在实际应用中,您可以将数据保存在内存中,进行加载和解析。
我在示例中使用了ADB,因为该工具提供了很好的数据生成示例,这些数据生成速度相对较慢,并且通过USB或Wi-Fi发送(同样也很慢),并且数据大小相对较大(对于具有复杂图片,PNG文件将为1M +)。
当我取消注释以下行时:
this_thread::sleep_for(chrono::milliseconds(50));
管道读取操作变得完全不可靠。该程序仅读取部分数据(大小无法预测)。
因此,即使只有50毫秒的短暂延迟,也会迫使异步管道的实现失败。
这不是正常情况。如果CPU使用率接近100%(即我们在高负载的服务器上)怎么办?如果线程运行可能在50毫秒或更短时间内执行的其他ASIO作业怎么办?因此,它只是基本的Boost ASIO错误的易于复制的实现:异步管道在您开始阅读它时不能容忍任何延迟;您必须在收到数据后立即再次致电async_read
,否则就有丢失数据的危险。
在实践中,当我使用相同的ASIO上下文运行多个作业(不仅有一个async_read
读取过程标准输出)时,async_pipe
在读取1M或更多数据的尝试中失败了50%
有人知道一种解决方法,如果ASIO上下文运行async_pipe
时,运行其他作业所需的延迟很小,那么如何使async_read
可靠并且不中断连接?