在C / C解析二进制消息流++ [英] Parsing binary message stream in C/C++

查看:196
本文介绍了在C / C解析二进制消息流++的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在写一个二进制协议(贾瓦德GRIL协议)脱$ C $铬。它由约一百消息的,具有以下列格式数据

I'm writing a decoder for a binary protocol (Javad GRIL protocol). It consists of about a hundred messages, with data in the following format:

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

中的字段其遵循相互没有间隙ANSI-CN codeD二进制数。解析这些消息的简单的方法是铸字节的输入阵列到合适的类型。的问题是,在流中的数据被打包,即,不对齐。

The fields are ANSI-encoded binary numbers which follow each other with no gaps. The simplest way to parse such messages is to cast an input array of bytes to the appropriate type. The problem is that the data in stream are packed, i.e. unaligned.

在x86这可以通过使用来解决的#pragma包(1)。然而,这不会对其他平台上工作,或会招致性能开销由于与未对齐数据进一步的工作。

On x86 this can be solved by using #pragma pack(1). However, that won't work on some other platforms or will incur performance overhead due to further work with misaligned data.

另一种方法是编写为每个消息类型的特定分析功能,但正如我所提到的,该协议包括数百个消息。

Another way is to write a specific parse function for each message type, but as I've mentioned, the protocol includes hundreds of messages.

然而,另一种选择是使用像Perl的解压()的功能和存储一些消息格式。就是说,我们可以的#define MsgDataFormatCfffC,然后调用解压(pMsgBody,MsgDataFormat)。这是要短得多,但仍容易出错和冗余。此外,该格式可以是更复杂的,因为消息可能包含阵列,所以解析器将是缓慢的和复杂的。

Yet another alternative is to use something like the Perl unpack() function and store the message format somewhere. Say, we can #define MsgDataFormat "CfffC" and then call unpack(pMsgBody, MsgDataFormat). This is much shorter but still error-prone and redundant. Moreover, the format can be more complicated because messages can contain arrays, so the parser will be slow and complex.

有没有共同的和有效的解决方案?我读过这篇文章并围绕谷歌搜索,但没有找到更好的办法来做到这一点。

Is there any common and effective solution? I've read this post and Googled around but didn't find a better way to do it.

也许C ++有一个解决方案?

Maybe C++ has a solution?

推荐答案

好吧,我下面的编译与VC10和GCC 4.5.1(上ideone.com )。我认为C ++ 1X这一切的需要,是<元组GT; ,这应可(如的std :: tr1 ::元组)为好。

Ok, the following compiles for me with VC10 and with GCC 4.5.1 (on ideone.com). I think all this needs of C++1x is <tuple>, which should be available (as std::tr1::tuple) in older compilers as well.

它仍然需要你输入一些code为每个成员,但这是很小code。 (见我的解释底。)

It still needs you to type some code for each member, but that is very minimal code. (See my explanation at the end.)

#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 6
initializing 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 键入仅包含三个数据成员,但是这只是用于测试。)

(I've shortened your MsgData type to only contain three data members, but this was just for testing.)

对于每一个消息类型,您需要定义其 BUFFER_SIZE 的静态常量和两个 get_tied_tuple()成员函数,有一个常量和一个非 - 常量,都以同样的方式来实现。 (当然,这也可以同样是非成员,但我试图让他们靠近,他们是绑数据成员的名单。)结果
对于某些类型(如的std ::字符串),您将需要添加那些特殊的重载详细:: read_value()详细:: write_value()功能。结果
机器的其余部分保持为所有消息类型相同。

For each message type, you need to define its buffer_size static constant and two get_tied_tuple() member functions, one const and one non-const, both implemented in the same way. (Of course, these could just as well be non-members, but I tried to keep them close to the list of data members they are tied to.)
For some types (like std::string) you will need to add special overloads of those detail::read_value() and detail::write_value() functions.
The rest of the machinery stays the same for all message types.

通过全C ++支持1X你也许能够摆脱不必完全输入了 get_tied_tuple的明确的返回类型()成员函数,但我并没有真正尝试过这一点。

With full C++1x support you might be able to get rid of having to fully type out the explicit return types of the get_tied_tuple() member functions, but I haven't actually tried this.

这篇关于在C / C解析二进制消息流++的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆