2011-01-20 26 views
6

Estoy escribiendo un decodificador para un protocolo binario (protocolo Javad GRIL). Se compone de un centenar de mensajes, con los datos en el siguiente formato:Analizando el flujo de mensajes binarios en C/C++

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

Los campos son números binarios codificados en ANSI que se suceden sin espacios. La forma más simple de analizar dichos mensajes es crear una matriz de entrada de bytes al tipo apropiado. El problema es que los datos en la secuencia están empaquetados, es decir, desalineados.

En x86 esto puede resolverse usando #pragma pack(1). Sin embargo, eso no funcionará en otras plataformas o incurrirá en gastos generales de rendimiento debido al trabajo adicional con datos mal alineados.

Otra forma es escribir una función de análisis específico para cada tipo de mensaje, pero como he mencionado, el protocolo incluye cientos de mensajes.

Otra alternativa es utilizar algo así como la función Perl unpack() y almacenar el formato de mensaje en alguna parte. Diga, podemos #define MsgDataFormat "CfffC" y luego llamar al unpack(pMsgBody, MsgDataFormat). Esto es mucho más corto pero aún propenso a errores y redundante. Además, el formato puede ser más complicado porque los mensajes pueden contener matrices, por lo que el analizador será lento y complejo.

¿Existe alguna solución común y efectiva? He leído this post y busqué en Google pero no encontré una mejor manera de hacerlo.

¿Quizás C++ tiene una solución?

+0

supongo usando tipos de tupla para definir los mensajes, puede escribir plantillas de funciones que iteren sobre miembros de tupla e invoque la función de extracción adecuada para el tipo que esté utilizando. Sin embargo, no se me ocurre una idea para la conversión automática de estas tuplas a estructuras. – sbi

+0

Suponiendo que está utilizando MSVC++ '#pragma pack (1)' debería funcionar incluso en otras plataformas. El embalaje se implementa en términos de cambios de bits y máscaras, no correcciones de alineación del sistema operativo. –

+0

Sus datos están sin pactar, sin alinear. Entonces, la forma correcta de hacerlo es el acceso por byte, como 'unpack' sugerido por @larsmans. – 9dan

Respuesta

1

La respuesta simple es no, si el mensaje es un formato binario específico que no se puede transmitir simplemente, no tiene más remedio que escribir un analizador para ello. Si tiene las descripciones de los mensajes (digamos xml o alguna forma de descripción fácil de analizar), ¿por qué no genera automáticamente el código de análisis a partir de esa descripción? No será tan rápido como un elenco, pero será mucho más rápido de generar que escribir cada mensaje a mano ...

3

Mi solución para analizar entradas binarias es usar una clase Reader, por cada entrada de mensaje que pueda define lo que se lee y el lector puede verificar si hay excesos, errores, ...

En caso de que:

msg.num = Reader.getChar(); 
msg.x = Reader.getFloat(); 
msg.y = Reader.getFloat(); 
msg.z = Reader.getFloat(); 
msg.elevation = Reader.getChar(); 

Todavía es mucho trabajo y propenso a errores, pero al menos ayuda a la comprobación de errores.

+3

"Reader Class" == 'std :: istream' o' std :: streambuf'. –

+0

@Billy: así es. He estado usando la clase Reader por un tiempo, así que nunca tuve un uso para un sistema más estándar. Bien descrito. – stefaanv

+1

Sí, pero esto es lo que llamo "escritura de rutina de análisis específico para cada mensaje") – gaga

7

Ok, las siguientes compilaciones para mí con VC10 y con GCC 4.5.1 (on ideone.com). Creo que todo esto necesita de C++ 1x es <tuple>, que debería estar disponible (como std::tr1::tuple) en compiladores más antiguos también.

Aún necesita que escriba algún código para cada miembro, pero ese es un código muy mínimo. (Véase mi explicación al final.)

#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; 
} 

Para mí esto imprime

 
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] 

(He acortado su tipo MsgData que sólo contienen tres miembros de datos, pero esto era sólo para las pruebas.)

Para cada tipo de mensaje, es necesario definir sus funciones buffer_size constantes estática y dos get_tied_tuple() miembro, un const y uno no const, tanto en marcha de la misma manera. (Por supuesto, estos podrían ser no miembros, pero traté de mantenerlos cerca de la lista de miembros de datos a los que están vinculados)
Para algunos tipos (como std::string) necesitará agregar sobrecargas especiales de esas detail::read_value() y detail::write_value() funciones.
El resto de la maquinaria se mantiene igual para todos los tipos de mensajes.

Con compatibilidad completa con C++ 1x, es posible que pueda deshacerse de tener que escribir completamente los tipos de devolución explícita de las funciones de miembro get_tied_tuple(), pero en realidad no lo he intentado.

+0

Wow. Eso es genial, necesito algo de tiempo para pensarlo) – gaga

+0

buen ejemplo para usar tupla ... hace una sintaxis bastante agradable. C++ 11 rocks. ¡Lo mejor es que proporciones la fuente completa en ideone.com! – oliver

1

No creo que pueda evitar escribir la rutina de análisis specicfic para cada mensaje en C++ puro (sin utilizar pragma).

Si todos sus mensajes son simples, POD, estructuras tipo C, creo que la solución más fácil sería escribir un generador de código: ponga sus estructuras en un encabezado sin otras cosas C++ y escriba un analizador simple (un perl/el script python/bash usando un par de expresiones regulares debería ser suficiente) -o busque uno- que pueda encontrar los nombres de las variables en cualquier mensaje; luego usarlo para generar automáticamente un código para cualquier mensaje para leerlo, como esto:

YourStreamType & operator>>(YourStreamType &stream, MsgData &msg) { 
    stream >> msg.num >> msg.x >> msg.y >> msg.z >> msg.elevation; 
    return stream; 
} 

especializan YourStreamType 's operator>> para cualquier tipo básico tus mensajes contienen y que se debe hacer:

MsgData msg; 
your_stream >> msg; 
0

siempre se puede alinear su memoria sí mismo:

uint8_t msg[TOTAL_SIZE_OF_THE_PARTS_OF_MsgData]; 

Como sizeof(MsgData) devuelve el tamaño de bytes de relleno MSGDATA +, se puede calcular

enum { TOTAL_SIZE_OF_THE_PARTS_OF_MsgData = 
    2*sizeof(uint8_t)+ 
    3*sizeof(float)+sizeof(THE_OTHER_FIELDS) 
} 

El uso de enumeraciones de tales constantes es un concepto bien probado en varias máquinas.

lee un mensaje binario en la matriz de mensajes. Más tarde se puede emitir los valores en los valores MSGDATA:

unsigned ofs = 0; 
MsgData M; 
M.num = (uint8_t)(&msg[ofs]); 
ofs += sizeof(M.num); 
M.x = (float)(&msg[ofs]); 
ofs += sizeof(M.x); 

y así sucesivamente ...

o utilizar memcpy si no te gusta el tipo de yesos:

memcpy(&M.x,&msg[ofs],sizeof(M.x)); ... 
Cuestiones relacionadas