2013-09-26 4 views
1

Я пытаюсь отправить необработанные данные в boost :: asio, поскольку boost :: сериализация слишком медленна для моих нужд. После различных примеров и повышающей документации, у меня есть клиент:Отправка необработанных данных по boost :: asio

SimulationClient:

void SimulationClient::sendData(std::vector<WaveformDefinition>waveformPackets) { 
     socket.async_send_to(boost::asio::buffer(waveformPackets), 
       receiver_endpoint, 
       boost::bind(&ClientEnvironmentEngine::sendComplete, this, 
         boost::asio::placeholders::error, 
         boost::asio::placeholders::bytes_transferred)); 
} 

Я попытался решением Tanner Sansbury в ниже, но не смогли заставить его работать. Тем не менее, я с успехом используя:

class WaveformReceiver { 
    WaveformDefinition *buffer; 

    WaveformReceiver(){ 
     buffer = new WaveformDefinition[MAX_WAVEFORMS]; 
     startReceive(); 
    } 

    void startReceive() { 
     socket_.async_receive_from(boost::asio::null_buffers(), remote_endpoint_, 
       boost::bind(&WaveformReceiver::handleReceive, this, 
       boost::asio::placeholders::error, 
       boost::asio::placeholders::bytes_transferred)); 
    } 

    void handleReceive(const boost::system::error_code& error, 
     std::size_t size/*bytes_transferred*/) 
    { 
      if (!error) 
      { 
       int available = socket_.available(); 
       int numWaveforms = available/sizeof(WaveformDefinition_c); 
       socket_.receive(boost::asio::buffer(buffer, available)); 

       //copy buffer into another buffer so we can re-use the original buffer for the next read 
       WaveformDefinition_c* tempBuffer = new WaveformDefinition_c[numWaveforms]; 
       std::memcpy (tempBuffer, buffer, available); 

       //schedule a thread to handle the array of waveforms that we copied 
       threadPool.schedule(boost::bind(handleWaveforms, tempBuffer, numWaveforms)); 
       //start listening for more waveforms 
       startReceive(); 
      } 
    } 
} 

Тэннер, или других, вы можете сказать мне, если то, что я делаю, тоже должны работать, или если я просто получаю повезло, что в настоящее время она работает?

ответ

3

Основная часть вопроса касается сериализации и десериализации коллекций.

Без управления компилятором и архитектурами как сервера, так и клиента отправка исходных структур обычно небезопасна, поскольку представление байтов может различаться между системами. Хотя компилятор и архитектура в этом конкретном случае одинаковы, #pragma pack(1) не имеет значения, так как WAVEFORM_DATA_STRUCT не записывается как сырая память в сокет. Вместо этого для сбора операции write предусмотрено несколько буферов памяти.

boost::array<boost::asio::mutable_buffer,2> buffer = {{ 
    boost::asio::buffer(&waveformPacket->numWaveforms, ...), // &numWaveforms 
    boost::asio::buffer(waveformPacket->waveforms)   // &waveforms[0] 
}}; 

Существуют различные инструменты, чтобы помочь с сериализации структуры данных, такие как Protocol Buffers.


В приведенном ниже коде будут продемонстрированы основы для сериализации структуры данных для сетевой связи. Чтобы упростить код и объяснение, я решил сосредоточиться на сериализации и десериализации, а не на записи и чтении из сокета. Другой пример, расположенный ниже этого раздела, отобразит больше необработанного подхода, который предполагает тот же компилятор и архитектуру.

Начиная с базовой foo типа:

struct foo 
{ 
    char a; 
    char b; 
    boost::uint16_t c; 
}; 

Это может быть определено, что данные могут быть упакованы в 4 общее количество байт. Ниже один возможный провод reprensetation:

0  8  16  24  32 
|--------+--------+--------+--------| 
| a | b |  c  | 
'--------+--------+--------+--------' 

С представлением проволоки определяется, две функции могут быть использованы для сериализации (сохранить) в foo объект в буфер, а другой может быть использована для десериализации (нагрузок) foo от А буфер. Поскольку foo.c больше байта, функции также должны будут учитывать endianness.Я решил использовать функции подстановки байтов в байтах в пространстве имен Boost.Asio для некоторой нейтральности платформы.

/// @brief Serialize foo into a network-byte-order buffer. 
void serialize(const foo& foo, unsigned char* buffer) 
{ 
    buffer[0] = foo.a; 
    buffer[1] = foo.b; 

    // Handle endianness. 
    using ::boost::asio::detail::socket_ops::host_to_network_short; 
    boost::uint16_t c = host_to_network_short(foo.c); 
    std::memcpy(&buffer[2], &c, sizeof c); 
} 

/// @brief Deserialize foo from a network-byte-order buffer. 
void deserialize(foo& foo, const unsigned char* buffer) 
{ 
    foo.a = buffer[0]; 
    foo.b = buffer[1]; 

    // Handle endianness. 
    using ::boost::asio::detail::socket_ops::network_to_host_short; 
    boost::uint16_t c; 
    std::memcpy(&c, &buffer[2], sizeof c); 
    foo.c = network_to_host_short(c); 
} 

С сериализации и десериализации сделано для foo, следующим шагом будет обрабатывать коллекцию foo объектов. Перед написанием кода необходимо определить проводное представление. В этом случае я решил префикс последовательности из foo элементов с 32-битным полем счетчика.

0  8  16  24  32 
|--------+--------+--------+--------| 
|  count of foo elements [n] | 
|--------+--------+--------+--------| 
|   serialized foo [0]  | 
|--------+--------+--------+--------| 
|   serialized foo [1]  | 
|--------+--------+--------+--------| 
|    ...    | 
|--------+--------+--------+--------| 
|   serialized foo [n-1]  | 
'--------+--------+--------+--------' 

Еще раз, два вспомогательных функций могут быть введены для сериализации и десериализации коллекцию foo объектов, а также необходимо учитывать порядка байтов поля счетчика.

/// @brief Serialize a collection of foos into a network-byte-order buffer. 
template <typename Foos> 
std::vector<unsigned char> serialize(const Foos& foos) 
{ 
    boost::uint32_t count = foos.size(); 

    // Allocate a buffer large enough to store: 
    // - Count of foo elements. 
    // - Each serialized foo object. 
    std::vector<unsigned char> buffer(
     sizeof count +   // count 
     foo_packed_size * count); // serialize foo objects 

    // Handle endianness for size. 
    using ::boost::asio::detail::socket_ops::host_to_network_long; 
    count = host_to_network_long(count); 

    // Pack size into buffer. 
    unsigned char* current = &buffer[0]; 
    std::memcpy(current, &count, sizeof count); 
    current += sizeof count; // Adjust position. 

    // Pack each foo into the buffer. 
    BOOST_FOREACH(const foo& foo, foos) 
    { 
    serialize(foo, current); 
    current += foo_packed_size; // Adjust position. 
    } 

    return buffer; 
}; 

/// @brief Deserialize a buffer into a collection of foo objects. 
std::vector<foo> deserialize(const std::vector<unsigned char>& buffer) 
{ 
    const unsigned char* current = &buffer[0]; 

    // Extract the count of elements from the buffer. 
    boost::uint32_t count; 
    std::memcpy(&count, current, sizeof count); 
    current += sizeof count; 

    // Handle endianness. 
    using ::boost::asio::detail::socket_ops::network_to_host_long; 
    count = network_to_host_long(count); 

    // With the count extracted, create the appropriate sized collection. 
    std::vector<foo> foos(count); 

    // Deserialize each foo from the buffer. 
    BOOST_FOREACH(foo& foo, foos) 
    { 
    deserialize(foo, current); 
    current += foo_packed_size; 
    } 

    return foos; 
}; 

Вот полный пример кода:

#include <iostream> 
#include <vector> 
#include <boost/asio.hpp> 
#include <boost/asio/detail/socket_ops.hpp> // endian functions 
#include <boost/cstdint.hpp> 
#include <boost/foreach.hpp> 
#include <boost/tuple/tuple.hpp>   // boost::tie 
#include <boost/tuple/tuple_comparison.hpp> // operator== for boost::tuple 

/// @brief Mockup type. 
struct foo 
{ 
    char a; 
    char b; 
    boost::uint16_t c; 
}; 

/// @brief Equality check for foo objects. 
bool operator==(const foo& lhs, const foo& rhs) 
{ 
    return boost::tie(lhs.a, lhs.b, lhs.c) == 
     boost::tie(rhs.a, rhs.b, rhs.c); 
} 

/// @brief Calculated byte packed size for foo. 
/// 
/// @note char + char + uint16 = 1 + 1 + 2 = 4 
static const std::size_t foo_packed_size = 4; 

/// @brief Serialize foo into a network-byte-order buffer. 
/// 
/// @detail Data is packed as follows: 
/// 
/// 0  8  16  24  32 
/// |--------+--------+--------+--------| 
/// | a | b |  c  | 
/// '--------+--------+--------+--------' 
void serialize(const foo& foo, unsigned char* buffer) 
{ 
    buffer[0] = foo.a; 
    buffer[1] = foo.b; 

    // Handle endianness. 
    using ::boost::asio::detail::socket_ops::host_to_network_short; 
    boost::uint16_t c = host_to_network_short(foo.c); 
    std::memcpy(&buffer[2], &c, sizeof c); 
} 

/// @brief Deserialize foo from a network-byte-order buffer. 
void deserialize(foo& foo, const unsigned char* buffer) 
{ 
    foo.a = buffer[0]; 
    foo.b = buffer[1]; 

    // Handle endianness. 
    using ::boost::asio::detail::socket_ops::network_to_host_short; 
    boost::uint16_t c; 
    std::memcpy(&c, &buffer[2], sizeof c); 
    foo.c = network_to_host_short(c); 
} 

/// @brief Serialize a collection of foos into a network-byte-order buffer. 
/// 
/// @detail Data is packed as follows: 
/// 
/// 0  8  16  24  32 
/// |--------+--------+--------+--------| 
/// |  count of foo elements [n] | 
/// |--------+--------+--------+--------| 
/// |   serialized foo [0]  | 
/// |--------+--------+--------+--------| 
/// |   serialized foo [1]  | 
/// |--------+--------+--------+--------| 
/// |    ...    | 
/// |--------+--------+--------+--------| 
/// |   serialized foo [n-1]  | 
/// '--------+--------+--------+--------' 
template <typename Foos> 
std::vector<unsigned char> serialize(const Foos& foos) 
{ 
    boost::uint32_t count = foos.size(); 

    // Allocate a buffer large enough to store: 
    // - Count of foo elements. 
    // - Each serialized foo object. 
    std::vector<unsigned char> buffer(
     sizeof count +   // count 
     foo_packed_size * count); // serialize foo objects 

    // Handle endianness for size. 
    using ::boost::asio::detail::socket_ops::host_to_network_long; 
    count = host_to_network_long(count); 

    // Pack size into buffer. 
    unsigned char* current = &buffer[0]; 
    std::memcpy(current, &count, sizeof count); 
    current += sizeof count; // Adjust position. 

    // Pack each foo into the buffer. 
    BOOST_FOREACH(const foo& foo, foos) 
    { 
    serialize(foo, current); 
    current += foo_packed_size; // Adjust position. 
    } 

    return buffer; 
}; 

/// @brief Deserialize a buffer into a collection of foo objects. 
std::vector<foo> deserialize(const std::vector<unsigned char>& buffer) 
{ 
    const unsigned char* current = &buffer[0]; 

    // Extract the count of elements from the buffer. 
    boost::uint32_t count; 
    std::memcpy(&count, current, sizeof count); 
    current += sizeof count; 

    // Handle endianness. 
    using ::boost::asio::detail::socket_ops::network_to_host_long; 
    count = network_to_host_long(count); 

    // With the count extracted, create the appropriate sized collection. 
    std::vector<foo> foos(count); 

    // Deserialize each foo from the buffer. 
    BOOST_FOREACH(foo& foo, foos) 
    { 
    deserialize(foo, current); 
    current += foo_packed_size; 
    } 

    return foos; 
}; 

int main() 
{ 
    // Create a collection of foo objects with pre populated data. 
    std::vector<foo> foos_expected(5); 
    char a = 'a', 
     b = 'A'; 
    boost::uint16_t c = 100; 

    // Populate each element. 
    BOOST_FOREACH(foo& foo, foos_expected) 
    { 
    foo.a = a++; 
    foo.b = b++; 
    foo.c = c++; 
    } 

    // Serialize the collection into a buffer. 
    std::vector<unsigned char> buffer = serialize(foos_expected); 

    // Deserialize the buffer back into a collection. 
    std::vector<foo> foos_actual = deserialize(buffer); 

    // Compare the two. 
    std::cout << (foos_expected == foos_actual) << std::endl; // expect 1 

    // Negative test. 
    foos_expected[0].c = 0; 
    std::cout << (foos_expected == foos_actual) << std::endl; // expect 0 
} 

Который дает ожидаемые результаты 1 и 0.


При использовании же компилятора и архитектуры, то это может быть возможным переинтерпретировать непрерывную последовательность foo объектов из исходного буфера в виде массива foo объектов, и заполнить std::vector<foo> с копией конструкторами. Например:

// Create and populate a contiguous sequence of foo objects. 
std::vector<foo> foo1; 
populate(foo1); 

// Get a handle to the contiguous memory block. 
const char* buffer = reinterpret_cast<const char*>(&foo1[0]); 

// Populate a new vector via iterator constructor. 
const foo* begin = reinterpret_cast<const foo*>(buffer); 
std::vector<foo> foos2(begin, begin + foos1.size()); 

В конце концов, foo1 должна быть равна foo2. Объекты foo в foo2 будут копироваться из переинтерпретированных foo объектов, находящихся в памяти, принадлежащих foo1.

#include <iostream> 
#include <vector> 
#include <boost/cstdint.hpp> 
#include <boost/foreach.hpp> 
#include <boost/tuple/tuple.hpp>   // boost::tie 
#include <boost/tuple/tuple_comparison.hpp> // operator== for boost::tuple 

/// @brief Mockup type. 
struct foo 
{ 
    char a; 
    char b; 
    boost::uint16_t c; 
}; 

/// @brief Equality check for foo objects. 
bool operator==(const foo& lhs, const foo& rhs) 
{ 
    return boost::tie(lhs.a, lhs.b, lhs.c) == 
     boost::tie(rhs.a, rhs.b, rhs.c); 
} 

int main() 
{ 
    // Create a collection of foo objects with pre populated data. 
    std::vector<foo> foos_expected(5); 
    char a = 'a', 
     b = 'A'; 
    boost::uint16_t c = 100; 

    // Populate each element. 
    BOOST_FOREACH(foo& foo, foos_expected) 
    { 
    foo.a = a++; 
    foo.b = b++; 
    foo.c = c++; 
    } 

    // Treat the collection as a raw buffer. 
    const char* buffer = 
     reinterpret_cast<const char*>(&foos_expected[0]); 

    // Populate a new vector. 
    const foo* begin = reinterpret_cast<const foo*>(buffer); 
    std::vector<foo> foos_actual(begin, begin + foos_expected.size()); 

    // Compare the two. 
    std::cout << (foos_expected == foos_actual) << std::endl; 

    // Negative test. 
    foos_expected[0].c = 0; 
    std::cout << (foos_expected == foos_actual) << std::endl; 
} 

Как и с другим подходом, это дает ожидаемые результаты 1 и 0.

+0

Это был невероятно подробный пример, спасибо. Тем не менее, похоже, что для выполнения этой сериализации и memcpy'ing может быть относительно медленным. Есть ли способ отправить данные через сокеты, просто беря кусок непрерывной памяти и вставляя ее в сокет? Особенно если мы контролируем континент на каждом конце соединения? – jekelija

+0

@jekelija: Да, сокеты работают только с необработанными данными; проблема заключается в десериализации.Я расширил ответ, чтобы подробно описать, как построить 'std :: vector ' путем переинтерпретации необработанного буфера в виде массива объектов 'foo'. Аналогичный подход должен быть доступен для создания объекта 'WAVEFORM_DATA_STRUCT' из буфера после того, как будет известна функция numWaveforms. –

+0

С трудом реализуя ваш подход, однако мне повезло с другим подходом, что я отредактировал свое оригинальное сообщение, чтобы включить ... ум, взглянув и посмотрев, правильно ли это? По сути, я де-сериализую непосредственно в массив Waveform (я понял, что моя исходная структура не нужна, так как я могу вычесть количество осциллограмм из объема данных, доступных для чтения) – jekelija

0

Во-первых, его небезопасно использовать pragma pack(1). Упаковка может отличаться от разных компиляторов/арки. Кроме того, у вас возникнут проблемы с изменением протокола. Вместо этого я предлагаю использовать google protobuf.

Второй. Вы отправляете std::vector, но фактические данные этого вектора не находятся внутри структуры WAVEFORM_DATA_STRUCT (вектор хранит свои данные в куче). Итак, вы отправляете вектор и указатель на кучу на другую машину, где этот указатель определенно недействителен. Вам нужно как-то сериализовать свой вектор.

P.S. Нет ничего общего с boost :: asio, это проблема правильной сериализации/десериализации.

+0

У нас есть контроль над клиентом и сервером, поэтому мы можем контролировать компиляторы и архитектуру таким образом, чтобы можно было сохранить упаковку pragma. Что касается вашего второго пункта, то мое понимание из документации по ускорению [link] (http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/reference/buffer/overload23.html) заключается в том, что буфер (вектор) по существу производит тот же результат, что и: mutable_buffers_1 ( data.size()? & data [0]: 0, data.size() * sizeof (PodType)); Это поэтому отправляет необработанные данные в памяти с адреса первого векторного элемента на адрес последнего векторного элемента – jekelija

+0

@jekelija об упаковке, который вы ищете для проблем. О данных - вы отправляете необработанные данные вектора, это не то, что вы думаете. Читайте о том, как вектор содержит свои данные. – PSIAlt

+0

@PSIAlt: 'boost :: asio :: buffer()' перегрузка для 'std :: vector' будет представлять буфер для содержимого' std :: vector' ('& v [0]'), а не 'std :: vector' (' & v'). Буфер Boost.Asio [reference counted buffer] (http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio/example/cpp03/buffers/reference_counted.cpp) создает «boost :: asio» :: const_buffer' из 'std :: vector '. –

Смежные вопросы