Основная часть вопроса касается сериализации и десериализации коллекций.
Без управления компилятором и архитектурами как сервера, так и клиента отправка исходных структур обычно небезопасна, поскольку представление байтов может различаться между системами. Хотя компилятор и архитектура в этом конкретном случае одинаковы, #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
.
Это был невероятно подробный пример, спасибо. Тем не менее, похоже, что для выполнения этой сериализации и memcpy'ing может быть относительно медленным. Есть ли способ отправить данные через сокеты, просто беря кусок непрерывной памяти и вставляя ее в сокет? Особенно если мы контролируем континент на каждом конце соединения? – jekelija
@jekelija: Да, сокеты работают только с необработанными данными; проблема заключается в десериализации.Я расширил ответ, чтобы подробно описать, как построить 'std :: vector' путем переинтерпретации необработанного буфера в виде массива объектов 'foo'. Аналогичный подход должен быть доступен для создания объекта 'WAVEFORM_DATA_STRUCT' из буфера после того, как будет известна функция numWaveforms. –
С трудом реализуя ваш подход, однако мне повезло с другим подходом, что я отредактировал свое оригинальное сообщение, чтобы включить ... ум, взглянув и посмотрев, правильно ли это? По сути, я де-сериализую непосредственно в массив Waveform (я понял, что моя исходная структура не нужна, так как я могу вычесть количество осциллограмм из объема данных, доступных для чтения) – jekelija