通过 boost::asio 发送原始数据
Sending raw data over boost::asio
我正在尝试通过boost::asio发送原始数据,因为boost::序列化对于我的需求来说太慢了。根据各种示例和提升文档,我有一个客户:
模拟客户端:
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();
}
}
}
坦纳,或者其他人,你能告诉我我正在做的事情是否也应该有效,或者我只是幸运地认为它目前正在工作吗?
问题的基本部分是关于序列化和反序列化集合。
如果不控制服务器和客户端的编译器和体系结构,发送原始结构通常是不安全的,因为系统之间的字节表示形式可能不同。 虽然编译器和体系结构在这种特定情况下是相同的,但#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]
}};
有多种工具可帮助序列化数据结构,例如协议缓冲区。
下面的代码将演示序列化网络通信数据结构的基础知识。 为了简化代码和解释,我选择专注于序列化和反序列化,而不是从套接字编写和读取。 本节下方的另一个示例将展示更多原始方法,该方法假定相同的编译器和体系结构。
从基本foo
类型开始:
struct foo
{
char a;
char b;
boost::uint16_t c;
};
可以确定数据可以打包成总共 4 个字节。 下面是一种可能的电线配置:
0 8 16 24 32
|--------+--------+--------+--------|
| a | b | c |
'--------+--------+--------+--------'
确定线路表示形式后,可以使用两个函数将foo
对象序列化(保存(到缓冲区,另一个函数可用于从缓冲区反序列化(加载(foo
。 由于foo.c
大于一个字节,因此函数还需要考虑字节序。 我选择在 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
对象的集合,并且还需要考虑 count 字段的字节顺序。
/// @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
. foo2
中的foo
对象将从驻留在 foo1
拥有的内存中的重新解释foo
对象进行复制构造。
#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
的预期结果。
首先,使用 pragma pack(1)
不安全。打包可能与不同的编译器/架构不同。此外,您会遇到协议更改的问题。我建议改用谷歌protobuf。
第二。您正在发送std::vector
但此向量的实际数据不在结构WAVEFORM_DATA_STRUCT
内(向量将其数据保存在堆中(。因此,您将向量及其指向堆的指针发送到另一台机器,该指针肯定无效。您需要以某种方式序列化向量。
附言与 boost::asio 无关,这个问题是关于正确的序列化/反序列化。
- C++:将值赋值给原始数据类型(例如布尔值)是原子操作吗?
- 从USBPcap库读取原始数据
- BoostSpirit.Qi-针对原始数据类型进行边界检查
- Qt - 将空指针(原始数据)转换为 QImage 并将其显示在标签上
- 使用 QDataStream 对原始数据进行反序列化
- 当原始数据是常量时,修改指针指向的位置是 UB 吗?
- IMAGE_SECTION_HEADER的虚拟地址和指针到原始数据的差异
- 直接从 RAM 发送原始数据
- MEX C++原始数据访问
- 服务器客户端通过原始数据错误C 发送接收结构
- 如何从C 中的卷曲中获取原始数据
- 在C/C 中创建和发送原始数据包
- 如何捕获麦克风缓冲区原始数据
- 用原始数据填充 std::vector
- opencv::Mat,从原始数据中获取像素值
- 使用 Qt 通过蓝牙发送多个原始数据包
- 将sqlite原始数据读取到qbyTearray
- 矢量调整大小抛出bad_alloc会使原始数据无效
- 从原始数据就地创建 std::vector
- 通过 boost::asio 发送原始数据