使用boost.process同时读取和写入child的stdio

时间:2018-02-08 05:11:55

标签: c++ windows boost boost-asio boost-process

我正在尝试使用boost.process使用类似的东西来编写和读取孩子的stdio:

boost::asio::io_service writeService, readService;
bp::async_pipe in{writeService};
bp::async_pipe out{readService};

bp::child process(CompressCmd.c_str(), bp::std_in < in, bp::std_out > out);
Buffer src;
src.reserve(4 * 1024 * 1024);
integer_type read = 0;
//std::atomic_int64_t totalWrite{0};
integer_type totalWrite = 0;
while (callback(CallbackActions::NeedMoreInput, src, read)) {
    in.async_write_some(
        boost::asio::buffer(src.data(), read),
        [](const boost::system::error_code &e, std::size_t) { });
    // written data is not important, that's why using same buffer
    out.async_read_some(boost::asio::buffer(src.data(), src.capacity()),
                        [&](const boost::system::error_code &e,
                           std::size_t byte_transferred) { totalWrite += byte_transferred; });
}
writeService.run();
in.close();
readService.run();

所有读写操作都被认定为成功,但totalWrite的值完全不正确f.e报告29356032,实际值应该在50000000左右   
我注意到程序正在中途终止,在readService.run()之后使用process.wait()冻结子,使用原子int产生相同的行为

现在我实际上只需要知道实际上有多少数据,这就是为什么我使用相同的缓冲区

1 个答案:

答案 0 :(得分:3)

  1. 此模式:

    while (callback(CallbackActions::NeedMoreInput, src, read)) {
        in.async_write_some(...);
        out.async_read_some(...);
    }
    

    很可能被误导(异步操作总是立即返回,因此您只需继续添加更多异步操作而不给他们机会运行)。

  2. 同样误导的是,您为管道提供了单独的服务,但是您完全以排除方式运行它们,因此在writeService完成之前不会运行任何读取操作。

  3. atomic类型被误导,因为多个线程无法访问

  4. 你想做什么?您保留一个大缓冲区但从不向其中放入任何数据(reserve!= resize)。因此,你只能希望什么都不写。

    更具讽刺意味的是,你正在完全相同的位置读取相同的缓冲区。但是,当你知道src.capacity()时,会立即Undefined Behaviour¹,因为你传递src.size()==0

    即使没有这个错误,你怎么能“同时”从内存中完全相同的字节进行读写,仍然知道预期的结果是什么?

  5. 您没有将自己的io_service传递给Boost进程

  6. 工作演示

    这是一份工作样本。当然,我不得不猜测你真正想做什么。

    我选择让程序将自己的源(main.cpp)发送到stdin,并迭代读取stdout,记录total_received个字节。然后打印退出代码和总数。

    作为一个make-shift压缩器,我使用'/usr/bin/xxd'因为它可用,甚至可以打印到std::cout进行调试。

    Live On Coliru //在Coliru上遇到麻烦

    #include <boost/asio.hpp>
    #include <boost/process.hpp>
    #include <boost/process/async.hpp>
    #include <iostream>
    std::vector<char> read_file(std::string const&);
    
    namespace bp = boost::process;
    using boost::system::error_code;
    
    using Loop = boost::function<void()>;
    using Buffer = std::array<char, 4*1024>;
    
    int main() {
        boost::asio::io_service svc;
    
        std::string const CompressCmd = "/usr/bin/xxd";
    
        bp::async_pipe in{svc}, out{svc};
        bp::child process(CompressCmd, bp::std_in < in, bp::std_out > out, svc);
    
        auto data = read_file("main.cpp");
    
        Loop read_loop, write_loop;
    
        Buffer recv_buffer;
        std::size_t total_received = 0;
        read_loop = [&read_loop, &out, &recv_buffer, &total_received] {
            out.async_read_some(boost::asio::buffer(recv_buffer),
                [&](error_code ec, size_t transferred) {
                    std::cout << "ReadLoop: " << ec.message() << " got " << transferred << " bytes\n";
                    total_received += transferred; 
                    if (!ec)
                        read_loop(); // continue reading
                });
        };
    
        boost::asio::async_write(in, boost::asio::buffer(data),
            [&](error_code ec, size_t transferred) {
                std::cout << "WriteLoop: " << ec.message() << " done, " << transferred << " bytes\n";
                in.close(ec);
                std::cout << "WriteLoop: closed pipe (" << ec.message() << ")\n";
            }); // async
    
        read_loop(); // async
    
        svc.run(); // Await all async operations
    
        std::cout << "Process exitcode " << process.exit_code() << ", total_received=" << total_received << "\n";
    }
    
    #include <fstream>
    #include <iterator>
    std::vector<char> read_file(std::string const& fname) {
        std::ifstream ifs(fname);
        return {std::istreambuf_iterator<char>(ifs), {}};
    }
    

    打印

    WriteLoop: Success done, 1787 bytes
    WriteLoop: closed pipe (Success)
    ReadLoop: Success got 4096 bytes
    ReadLoop: Success got 3515 bytes
    ReadLoop: End of file got 0 bytes
    Process exitcode 0, total_received=7611
    

    解释,简化

    请注意,我们在没有循环的情况下完成所有的写作。这是因为boost::asio::async_write是一个组合操作(它隐藏了循环)。

    同样,如果你能“负担得起”将整个收到的数据存储在内存中,你可以使用boost::asio::streambuf并使用类似的组合操作来简化:

    Live On Coliru //在Coliru上遇到麻烦

    #include <boost/asio.hpp>
    #include <boost/process.hpp>
    #include <boost/process/async.hpp>
    #include <iostream>
    std::vector<char> read_file(std::string const&);
    
    namespace bp = boost::process;
    using boost::system::error_code;
    
    int main() {
        boost::asio::io_service svc;
    
        std::string const CompressCmd = "/usr/bin/xxd";
    
        bp::async_pipe in{svc}, out{svc};
        bp::child process(CompressCmd, bp::std_in < in, bp::std_out > out, svc);
    
        auto data = read_file("main.cpp");
    
        boost::asio::streambuf recv_buffer;
        boost::asio::async_read(out, recv_buffer,
                [&](error_code ec, size_t transferred) {
                    std::cout << "ReadLoop: " << ec.message() << " got " << transferred << " bytes\n";
                });
    
        boost::asio::async_write(in, boost::asio::buffer(data),
            [&](error_code ec, size_t transferred) {
                std::cout << "WriteLoop: " << ec.message() << " done, " << transferred << " bytes\n";
                in.close(ec);
                std::cout << "WriteLoop: closed pipe (" << ec.message() << ")\n";
            }); // async
    
        svc.run(); // Await all async operations
    
        std::cout << "Process exitcode " << process.exit_code() << ", total_received=" << recv_buffer.size() << "\n";
    }
    
    #include <fstream>
    #include <iterator>
    std::vector<char> read_file(std::string const& fname) {
        std::ifstream ifs(fname);
        return {std::istreambuf_iterator<char>(ifs), {}};
    }
    
      

    相反,如果你在发送之前无法承担内存中的所有数据,你可以创建一个循环来逐块发送输入

    两个异步循环,带延迟

    让我们这样做,并通过在编写每个块之前延迟一秒来使其更有趣。您期望看到的是由于延迟而发生的交替读/写:

    Live On Coliru //在Coliru上运行

    #include <boost/asio.hpp>
    #include <boost/asio/high_resolution_timer.hpp>
    #include <boost/process.hpp>
    #include <boost/process/async.hpp>
    #include <iostream>
    #include <fstream>
    
    namespace bp = boost::process;
    using boost::system::error_code;
    using namespace std::chrono_literals;
    
    using Loop = boost::function<void()>;
    using Buffer = std::array<char, 500>;
    
    int main() {
        boost::asio::io_service svc;
        auto on_exit = [](int code, std::error_code ec) {
                std::cout << "Exited " << code << " (" << ec.message() << ")\n";
            };
    
        std::string const CompressCmd = "/usr/bin/xxd";
    
        bp::async_pipe in{svc}, out{svc};
        bp::child process(CompressCmd, bp::std_in < in, bp::std_out > out, svc, bp::on_exit(on_exit));
    
        Loop read_loop, write_loop;
    
        Buffer recv_buffer;
        std::size_t total_received = 0;
        read_loop = [&read_loop, &out, &recv_buffer, &total_received] {
            out.async_read_some(boost::asio::buffer(recv_buffer),
                [&](error_code ec, size_t transferred) {
                    std::cout << "ReadLoop: " << ec.message() << " got " << transferred << " bytes\n";
                    total_received += transferred; 
                    if (!ec)
                        read_loop(); // continue reading
                });
        };
    
        std::ifstream ifs("main.cpp");
        std::size_t total_written = 0;
        Buffer send_buffer;
        boost::asio::high_resolution_timer send_delay(svc);
        write_loop = [&write_loop, &in, &ifs, &send_buffer, &total_written, &send_delay] {
            if (!ifs.good())
            {
                error_code ec;
                in.close(ec);
                std::cout << "WriteLoop: closed stdin (" << ec.message() << ")\n";
                return;
            }
            ifs.read(send_buffer.data(), send_buffer.size());
    
            boost::asio::async_write(in, boost::asio::buffer(send_buffer.data(), ifs.gcount()),
                [&](error_code ec, size_t transferred) {
                    std::cout << "WriteLoop: " << ec.message() << " sent " << transferred << " bytes\n";
                    total_written += transferred; 
                    if (!ec) {
                        send_delay.expires_from_now(1s);
                        send_delay.async_wait([&write_loop](error_code ec) {
                            std::cout << "WriteLoop: send delay " << ec.message() << "\n";
                            if (!ec) write_loop(); // continue writing
                        });
                    }
                });
        };
    
        read_loop(); // async
        write_loop(); // async
    
        svc.run(); // Await all async operations
    
        std::cout << "Process exitcode " << process.exit_code() << ", total_received=" << total_received << "\n";
    }
    

    打印

    WriteLoop: Success sent 500 bytes
    WriteLoop: send delay Success
    WriteLoop: Success sent 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 96 bytes
    WriteLoop: send delay Success
    WriteLoop: Success sent 500 bytes
    WriteLoop: send delay Success
    WriteLoop: Success sent 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 96 bytes
    WriteLoop: send delay Success
    WriteLoop: Success sent 500 bytes
    WriteLoop: send delay Success
    WriteLoop: Success sent 134 bytes
    WriteLoop: send delay Success
    WriteLoop: closed stdin (Success)
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 500 bytes
    ReadLoop: Success got 22 bytes
    Exited 0 (Success)
    ReadLoop: End of file got 0 bytes
    Process exitcode 0, total_received=11214
    

    ¹或许只是未指明,我现在不想发现差异