Примечание для тех, кто голосует: этот вопрос касается не асинхронных аспектов asio (хотя, возможно, здесь может иметь смысл асинхронное решение, что я и предлагаю в конце). На самом деле это просто использование streambuf и ostream с оболочкой asio tcp socket. Примеры/учебник не охватывают этот конкретный аспект (разделение вызова на запись).
Я пишу некоторый (надеюсь, простой) код для среды плагина, которому необходимо отправить довольно большой фрагмент данных (~ 2 МБ) на внешний сервер в ответ на какое-то событие. Данные нужно отправлять достаточно быстро и полностью, но это нечасто, и я не слишком беспокоюсь о производительности. Я использую буферы протокола Google для сериализации данных.
На данный момент у меня есть следующий код, который почти работает:
#include <boost/asio.hpp>
// connect to the server:
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query(server_address, server_port);
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::socket socket(io_service);
boost::asio::connect(socket, endpoint_iterator);
// float_array consists of ~500,000 floats in a ProtoBuf message:
//
// message FloatArray {
// repeated float data = 1 [packed=true];
// }
// send the serialized float_array to the server:
boost::asio::streambuf b;
std::ostream os(&b);
float_array.SerializeToOstream(&os);
boost::asio::write(socket, b);
// the TCP connection *must* now close to signal the server
Проблема заключается в том, что среда, в которой я работаю (многопоточность), видит операцию write()
как слишком длинную (блокирует) и завершает поток. Поскольку мне не разрешено создавать свои собственные потоки, мне нужно разделить операцию write() на несколько отдельных записей.
Моя забота - как это сделать. Я знаю, что могу использовать это для отправки точного количества байтов:
boost::asio::write(socket, b, boost::asio::transfer_exactly(65536));
Но для этого правильно мне нужно точно знать, сколько байтов осталось в Ostream. Я замечаю, что b.size()
соответственно сокращается, поэтому я могу использовать это. Однако, чтобы разделить мои записи, мне нужно сохранить некоторое состояние между вызовами этой функции.
Один из вариантов - хранить как streambuf b, так и ostream os между вызовами для моей функции записи, но мне интересно, есть ли лучший способ сделать это. Насколько я могу судить, невозможно частично сериализовать выход ProtoBuf, поэтому я думаю, что я застрял в одном вызове float_array.SerializeToOstream()
. Вопрос в том, есть ли надлежащий способ напрямую запросить ostream для количества доступных байтов или, возможно, использовать какой-либо другой механизм (boost::asio::buffer
возможно?).
Я с удовольствием рассмотрю документацию boost :: asio, я просто ищу небольшое руководство по продолжению работы, так как там есть много документации для работы, и я не уверен, какие части головоломки имеют значение.
Мысль - возможно ли использовать boost :: asio для создания своего рода однопотокового асинхронного "отправителя" по этим строкам, который может обрабатывать такое состояние для меня? Например, могу ли я вызвать какую-то неблокирующую функцию write()
а затем иметь какой-то обратный вызов (или часто посещаемую функцию), который проверяет завершение и затем закрывает TCP-соединение?
Хотя я не смог найти способ запросить ostream для определения количества "ожидания" данных в потоке напрямую, я смог полностью исключить ostream и сериализовать данные в массив char*
. Это можно затем отправить аналогично методу сокета старого сокета с функцией boost::asio::write()
:
...
tcp::socket socket(io_service);
char * buffer = new char[size]; // or a smart-ptr
float_array.SerializeToArray(static_cast<void*>(buffer, size));
void * p = static_cast<void*>(buffer);
int bytes_sent = boost::asio::write(socket, boost::asio::buffer(p, bytes_to_send);
В качестве альтернативы, если использование boost::asio::streambuf
и std::ostream
являются предпочтительными, то оказывается, что streambuf может быть запрошен (с .size()
) после того, как ostream был использован для записи некоторых данных:
boost::asio::streambuf b;
std::ostream os(&b);
float_array.SerializeToOstream(&os);
// send a chunk of a particular size
int bytes_to_send = std::min(chunk_size, b.size());
cout << b.size() << endl; // shows amount of remaining data
boost::asio::write(socket, b, boost::asio::transfer_exactly(bytes_to_send));
cout << b.size() << endl; // shows a reduction in amount of remaining data
Поэтому, если это вызывается несколько раз (для каждого фрагмента), то необходимо обновить ostream, streambuf и io_service.