gpt4 book ai didi

c++ - 在 C/C++ 中解析二进制消息流

转载 作者:塔克拉玛干 更新时间:2023-11-02 23:15:07 27 4
gpt4 key购买 nike

我正在为二进制协议(protocol)(Javad GRIL 协议(protocol))编写解码器。它由大约一百条消息组成,数据格式如下:

struct MsgData {
uint8_t num;
float x, y, z;
uint8_t elevation;
...
};

这些字段是 ANSI 编码的二进制数字,它们彼此紧随其后,没有间隙。解析此类消息的最简单方法是将输入的字节数组转换为适当的类型。问题是流中的数据是打包的,即未对齐。

在 x86 上,这可以通过使用 #pragma pack(1) 来解决。但是,这在其他一些平台上不起作用,或者由于进一步处理未对齐的数据而导致性能开销。

另一种方法是为每种消息类型编写一个特定的解析函数,但正如我所提到的,该协议(protocol)包含数百条消息。

另一种替代方法是使用类似于 Perl unpack() 函数的方法并将消息格式存储在某处。比如,我们可以#define MsgDataFormat "CfffC" 然后调用unpack(pMsgBody, MsgDataFormat)。这要短得多,但仍然容易出错且多余。此外,格式可能会更复杂,因为消息可以包含数组,因此解析器会很慢而且很复杂。

有什么通用有效的解决方法吗?我读过 this post并用 Google 搜索但没有找到更好的方法。

也许 C++ 有解决方案?

最佳答案

好的,下面的代码是用 VC10 和 GCC 4.5.1 ( on ideone.com ) 为我编译的。我认为 C++1x 的所有这些需求是 <tuple> ,在旧的编译器中也应该可用(如 std::tr1::tuple )。

它仍然需要您为每个成员键入一些代码,但这是非常少的代码。 (最后看我的解释。)

#include <iostream>
#include <tuple>

typedef unsigned char uint8_t;
typedef unsigned char byte_t;

struct MsgData {
uint8_t num;
float x;
uint8_t elevation;

static const std::size_t buffer_size = sizeof(uint8_t)
+ sizeof(float)
+ sizeof(uint8_t);

std::tuple<uint8_t&,float&,uint8_t&> get_tied_tuple()
{return std::tie(num, x, elevation);}
std::tuple<const uint8_t&,const float&,const uint8_t&> get_tied_tuple() const
{return std::tie(num, x, elevation);}
};

// needed only for test output
inline std::ostream& operator<<(std::ostream& os, const MsgData& msgData)
{
os << '[' << static_cast<int>(msgData.num) << ' '
<< msgData.x << ' ' << static_cast<int>(msgData.elevation) << ']';
return os;
}

namespace detail {

// overload the following two for types that need special treatment
template<typename T>
const byte_t* read_value(const byte_t* bin, T& val)
{
val = *reinterpret_cast<const T*>(bin);
return bin + sizeof(T)/sizeof(byte_t);
}
template<typename T>
byte_t* write_value(byte_t* bin, const T& val)
{
*reinterpret_cast<T*>(bin) = val;
return bin + sizeof(T)/sizeof(byte_t);
}

template< typename MsgTuple, unsigned int Size = std::tuple_size<MsgTuple>::value >
struct msg_serializer;

template< typename MsgTuple >
struct msg_serializer<MsgTuple,0> {
static const byte_t* read(const byte_t* bin, MsgTuple&) {return bin;}
static byte_t* write(byte_t* bin, const MsgTuple&) {return bin;}
};

template< typename MsgTuple, unsigned int Size >
struct msg_serializer {
static const byte_t* read(const byte_t* bin, MsgTuple& msg)
{
return read_value( msg_serializer<MsgTuple,Size-1>::read(bin, msg)
, std::get<Size-1>(msg) );
}
static byte_t* write(byte_t* bin, const MsgTuple& msg)
{
return write_value( msg_serializer<MsgTuple,Size-1>::write(bin, msg)
, std::get<Size-1>(msg) );
}
};

template< class MsgTuple >
inline const byte_t* do_read_msg(const byte_t* bin, MsgTuple msg)
{
return msg_serializer<MsgTuple>::read(bin, msg);
}

template< class MsgTuple >
inline byte_t* do_write_msg(byte_t* bin, const MsgTuple& msg)
{
return msg_serializer<MsgTuple>::write(bin, msg);
}
}

template< class Msg >
inline const byte_t* read_msg(const byte_t* bin, Msg& msg)
{
return detail::do_read_msg(bin, msg.get_tied_tuple());
}

template< class Msg >
inline const byte_t* write_msg(byte_t* bin, const Msg& msg)
{
return detail::do_write_msg(bin, msg.get_tied_tuple());
}

int main()
{
byte_t buffer[MsgData::buffer_size];

std::cout << "buffer size is " << MsgData::buffer_size << '\n';

MsgData msgData;
std::cout << "initializing data...";
msgData.num = 42;
msgData.x = 1.7f;
msgData.elevation = 17;
std::cout << "data is now " << msgData << '\n';
write_msg(buffer, msgData);

std::cout << "clearing data...";
msgData = MsgData();
std::cout << "data is now " << msgData << '\n';

std::cout << "reading data...";
read_msg(buffer, msgData);
std::cout << "data is now " << msgData << '\n';

return 0;
}

对我来说这打印

buffer size is 6initializing data...data is now [0x2a 1.7 0x11]clearing data...data is now [0x0 0 0x0]reading data...data is now [0x2a 1.7 0x11]

(我已将您的 MsgData 类型缩短为仅包含三个数据成员,但这只是为了测试。)

对于每种消息类型,您需要定义其 buffer_size静态常量和两个 get_tied_tuple()成员函数,一个const和一个非 const ,两者的实现方式相同。 (当然,这些也可以是非成员,但我尽量让它们靠近它们所绑定(bind)的数据成员列表。)
对于某些类型(如 std::string ),您需要添加那些 detail::read_value() 的特殊重载和 detail::write_value()函数。
其余机制对于所有消息类型都保持不变。

有了完整的 C++1x 支持,您也许可以摆脱必须完全键入 get_tied_tuple() 的显式返回类型的麻烦。成员函数,但我还没有真正尝试过这个。

关于c++ - 在 C/C++ 中解析二进制消息流,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4749325/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com