1

使用 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在 50% 的读取 1M 或更多数据的尝试中失败。

如果 ASIO 上下文运行时运行其他作业所需的延迟非常小,有谁知道如何建立async_pipe可靠而不中断连接的解决方法?async_read

4

0 回答 0