С помощью 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 (так же медленно), и размер данных является сравнительно большим (для устройства Full HD со сложным изображением файл PNG будет 1M +).
Когда я раскомментирую следующую строку:
this_thread::sleep_for(chrono::milliseconds(50));
Операция чтения канала становится совершенно ненадежной.Программа читает только часть данных (непредсказуемого размера).
Таким образом, даже такая короткая задержка в 50 миллисекунд заставляет ускорить реализацию асинхронного канала.
Это ненормальная ситуация.Что делать, если загрузка процессора составляет около 100% (т.е. мы находимся на сильно загруженном сервере)?Что, если поток запускает другие задания ASIO, которые могут выполняться в течение 50 миллисекунд или меньше?Таким образом, это просто легко воспроизводимая реализация фундаментальной ошибки ASIO boost: асинхронный канал не может допустить каких-либо задержек, когда вы начали его читать;Вы должны снова вызвать async_read
сразу после получения данных, в противном случае вы рискуете потерять свои данные.
На практике, когда я использую один и тот же контекст ASIO для запуска нескольких заданий (не только одного async_read
)который читает стандартный вывод процесса), async_pipe
терпит неудачу в 50% попыток прочитать 1M данных или более.
Кто-нибудь знает обходной путь, как сделать async_pipe
надежным и не разрывать соединение, если контекст ASIOзапускает async_read
с очень небольшими задержками, необходимыми для запуска других заданий?