2011-01-20 11 views
6

Sto scrivendo un decodificatore per un protocollo binario (protocollo Javil GRIL). Si compone di circa un centinaio di messaggi, con i dati nel seguente formato:Analisi del flusso di messaggi binari in C/C++

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

I campi sono numeri binari con codifica ANSI che si susseguono senza spazi. Il modo più semplice per analizzare questi messaggi consiste nel trasmettere una matrice di input di byte al tipo appropriato. Il problema è che i dati nel flusso sono imballati, vale a dire non allineati.

Su x86 questo può essere risolto utilizzando #pragma pack(1). Tuttavia, ciò non funzionerà su altre piattaforme o causerà un sovraccarico delle prestazioni a causa di un ulteriore lavoro con dati disallineati.

Un altro modo è scrivere una funzione di analisi specifica per ogni tipo di messaggio, ma come ho menzionato, il protocollo include centinaia di messaggi.

Un'altra alternativa è quella di utilizzare qualcosa come la funzione Perl unpack() e memorizzare il formato del messaggio da qualche parte. Dì, possiamo #define MsgDataFormat "CfffC" e poi chiamare unpack(pMsgBody, MsgDataFormat). Questo è molto più breve ma ancora soggetto a errori e ridondante. Inoltre, il formato può essere più complicato perché i messaggi possono contenere array, quindi il parser sarà lento e complesso.

Esiste una soluzione comune ed efficace? Ho letto this post e ho cercato su Google ma non ho trovato un modo migliore per farlo.

Forse C++ ha una soluzione?

+0

suppongo utilizzando tipi di tupla per definire i messaggi, è possibile scrivere modelli di funzioni che iterano su membri di tuple e invocare la funzione di estrazione appropriata per qualsiasi tipo si stia utilizzando. Tuttavia, non riesco a trovare un'idea per la conversione automagicamente da queste tuple alle strutture. – sbi

+0

Supponendo che tu stia usando MSVC++ '#pragma pack (1)' dovrebbe funzionare anche su altre piattaforme. L'imballaggio è implementato in termini di bit shift e maschere, non di correzioni di allineamento del sistema operativo. –

+0

I tuoi dati sono non elaborati, non allineati. Quindi, il modo corretto per farlo è l'accesso a byte come "unpack" suggerito da @larsmans. – 9dan

risposta

1

La risposta semplice è no, se il messaggio è uno specifico formato binario che non può essere semplicemente castato, non c'è altra scelta che scrivere un parser per esso. Se si hanno le descrizioni dei messaggi (ad esempio xml o qualche forma di descrizione facilmente analizzata), perché non si genera automaticamente il codice di analisi da quella descrizione? Non sarà veloce come un cast, ma sarà dannatamente veloce a generare più che scrivere ogni messaggio a mano ...

3

La mia soluzione per l'analisi dell'ingresso binario è di usare una classe Reader, quindi per messaggio è possibile definire ciò che viene letto e il lettore può controllare eccedenze, underrun, ...

In caso si:

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

è ancora un sacco di lavoro e soggetto a errori, ma almeno aiuta controllo degli errori.

+3

"Classe lettore" == 'std :: istream' o' std :: streambuf'. –

+0

@Billy: così è. Ho usato la classe Reader per un po ', quindi non ho mai avuto un uso per un sistema più standard. Ben individuato. – stefaanv

+1

Sì, ma questo è ciò che io chiamo "scrivere una routine di analisi speci fi ca per ogni messaggio") – gaga

7

Ok, il seguente compila per me con VC10 e con GCC 4.5.1 (on ideone.com). Penso che tutte queste esigenze di C++ 1x siano <tuple>, che dovrebbe essere disponibile (come std::tr1::tuple) anche nei compilatori più vecchi.

Ha ancora bisogno di digitare del codice per ogni membro, ma questo è un codice molto minimale. (Vedi la mia spiegazione alla fine.)

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

Per me Questo stampa

 
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] 

(Ho abbreviato il tuo tipo MsgData per contenere solo tre membri di dati, ma questo era solo per i test.)

Per ciascun tipo di messaggio, è necessario definire le sue funzioni buffer_size statica costante e due get_tied_tuple() membri, uno const e uno non const, sia implementata nello stesso modo. (Naturalmente, questi potrebbero benissimo essere i non soci, ma ho cercato di tenerli vicino alla lista dei membri di dati che sono legati a.)
Per alcuni tipi (come std::string) sarà necessario aggiungere sovraccarichi speciali quelle detail::read_value() e detail::write_value() funzioni.
Il resto della macchina rimane uguale per tutti i tipi di messaggi.

Con il supporto completo di C++ 1x potresti riuscire a eliminare completamente i tipi di ritorno esplicito delle funzioni membro get_tied_tuple(), ma in realtà non l'ho provato.

+0

Wow. È bello, ho bisogno di un po 'di tempo per pensarci) – gaga

+0

un bell'esempio per l'uso della tupla ... rende la sintassi piuttosto buona. C++ 11 rocks.Tutto il meglio che fornisci l'intero sito su ideone.com! – oliver

1

Non penso che si possa evitare lo di scrittura di speci fi ca routine per ogni messaggio in puro C++ (senza usare pragma).

Se tutti i messaggi sono semplici, POD, strutture simili a C, penso che la soluzione più semplice sarebbe scrivere un generatore di codice: inserire le proprie strutture in un'intestazione senza altro materiale C++ e scrivere un parser semplice (un perl/lo script python/bash che usa un paio di espressioni regolari dovrebbe essere sufficiente) o, se lo si cerca, è in grado di trovare i nomi delle variabili in qualsiasi messaggio; quindi utilizzarlo per generare automaticamente un codice per ogni messaggio di leggerlo, in questo modo:

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

specializzati YourStreamType s' operator>> per qualsiasi tipo di base i messaggi contengono e si dovrebbe fare di più:

MsgData msg; 
your_stream >> msg; 
0

È sempre possibile allineare la tua memoria te stesso:

uint8_t msg[TOTAL_SIZE_OF_THE_PARTS_OF_MsgData]; 

Come sizeof(MsgData) restituisce la dimensione di byte di riempimento MSGDATA +, è in grado di calcolare

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

Uso enumerazioni per tali costanti è un concetto ben collaudato su più macchine.

leggere un messaggio binario nella matrice msg. In seguito si può lanciare i valori nei valori 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); 

e così via ...

o utilizzare memcpy se non vi piace il tipo di calchi:

memcpy(&M.x,&msg[ofs],sizeof(M.x)); ... 
Problemi correlati