2009-05-12 21 views
5

Envío y recepción de datos binarios a/desde un dispositivo en paquetes (64 bytes). Los datos tienen un formato específico, partes del cual varían con diferentes solicitudes/respuestas.¿Cómo interpretar datos binarios en C++?

Ahora estoy diseñando un intérprete para los datos recibidos. Simplemente leer los datos por posiciones está bien, pero no se ve tan bien cuando tengo una docena de formatos de respuesta diferentes. Actualmente estoy pensando en crear algunas estructuras para ese propósito, pero no sé cómo funcionará con el relleno.

¿Quizás hay una manera mejor?


relacionadas:

+0

Estaba seguro de haber visto un duplicado cercano de esto, pero no pude desenterrarlo. Las respuestas en el enlace relacionado no son tan buenas como las que están aquí, pero aún así ... – dmckee

Respuesta

3

He hecho esto innumerables veces: es un escenario muy común. Hay una serie de cosas que hago casi siempre.

No se preocupe demasiado por hacerlo la cosa más eficiente disponible.

Si terminamos pasando mucho tiempo empaquetando y desempacando paquetes, entonces siempre podemos cambiarlo para que sea más eficiente. Aunque todavía no he encontrado un caso en el que haya tenido que hacerlo, ¡no he estado implementando enrutadores de red!

Si bien el uso de structs/unions es el enfoque más eficiente en términos de tiempo de ejecución, presenta una serie de complicaciones: convencer al compilador de empaquetar las estructuras/uniones para que coincidan con la estructura de octetos de los paquetes que necesita; problemas de alineación y endianness, y una falta de seguridad ya que no hay o hay poca oportunidad de hacer verificaciones de cordura en las compilaciones de depuración.

a menudo terminan con una arquitectura que incluye los siguientes tipos de cosas: clase base

  • un paquete. Todos los campos de datos comunes son accesibles (pero no modificables).Si los datos no se almacenan en un formato empaquetado, entonces hay una función virtual que producirá un paquete empaquetado.
  • Varias clases de presentación para tipos de paquetes específicos, derivadas del tipo de paquete común. Si estamos utilizando una función de embalaje, entonces cada clase de presentación debe implementarlo.
  • Cualquier cosa que pueda inferirse del tipo específico de la clase de presentación (es decir, un id del tipo de paquete de un campo de datos común), se trata como parte de la inicialización y no puede modificarse.
  • Cada clase de presentación se puede construir a partir de un paquete desempaquetado, o fallará elegantemente si los datos del paquete no son válidos para ese tipo. Esto puede ser envuelto en una fábrica por conveniencia.
  • Si no tenemos RTTI disponible, podemos obtener "RTTI de un pobre" utilizando el identificador de paquete para determinar qué clase de presentación específica es realmente un objeto.

En todo esto, es posible (incluso si solo para las compilaciones de depuración) verificar que cada campo que se puede modificar se está estableciendo en un valor razonable. Si bien puede parecer mucho trabajo, es muy difícil tener un paquete con formato no válido, el contenido de los paquetes preenvasados ​​puede ser revisado fácilmente mediante un depurador (ya que todo está en las variables normales de formato nativas de la plataforma).

Si tenemos que implementar un esquema de almacenamiento más eficiente, eso también puede ser envuelto en esta abstracción con un pequeño costo de rendimiento adicional.

+0

Este tipo de diseño funciona muy bien para flujos de datos codificados en ASN.1. Acabo de implementar algo como esto yo mismo para analizar LDAP – Matt

3

Es difícil decir cuál es la mejor solución es sin conocer el formato exacto (s) de los datos. ¿Has considerado usar uniones?

+0

¡Gracias! Una muy buena idea, aunque necesitaría una estructura o incluso un par de ellas de todos modos. –

+3

Creo que implicó estructuras y uniones usadas juntas. Pero aún podría haber problemas relacionados con agujeros de estructura. –

8

Necesita utilizar structs yo uniones. Tendrá que asegurarse de que sus datos estén correctamente empaquetados en ambos lados de la conexión y que desee traducir hacia y desde el orden de bytes de la red en cada extremo si hay alguna posibilidad de que cualquiera de los lados de la conexión se ejecute con un endianess.

A modo de ejemplo:

#pragma pack(push) /* push current alignment to stack */ 
#pragma pack(1)  /* set alignment to 1 byte boundary */ 
typedef struct { 
    unsigned int packetID; // identifies packet in one direction 
    unsigned int data_length; 
    char   receipt_flag; // indicates to ack packet or keep sending packet till acked 
    char   data[]; // this is typically ascii string data w/ \n terminated fields but could also be binary 
} tPacketBuffer ; 
#pragma pack(pop) /* restore original alignment from stack */ 

y luego al asignar:

packetBuffer.packetID = htonl(123456); 

y luego al recibir:

packetBuffer.packetID = ntohl(packetBuffer.packetID); 

Estas son algunas discusiones de Endianness y Alignment and Structure Packing

Si no empaqueta la estructura, terminará alineada con los límites de las palabras y el diseño interno de la estructura y su tamaño serán incorrectos.

+4

Tenga en cuenta que en algunos procesadores como ARM puede obtener una excepción de alineación de datos si intentó acceder data_length en el ejemplo. – Steven

+0

@Steven buen punto. –

+0

Para solucionar el problema que Steven mencionó sobre la alineación de datos, deberá agregar el panel de caracteres [3]; después de receipt_flag. – zooropa

1

Estoy de acuerdo con Wuggy. También puede usar la generación de código para hacer esto. Utilice un archivo de definición de datos simple para definir todos sus tipos de paquetes, luego ejecute un script de Python sobre él para generar estructuras prototipo y funciones de serialización/deserialización para cada uno.

1

Esta es una solución "lista para usar", pero le sugiero echar un vistazo a la biblioteca Python construct.

Construct es una biblioteca de Python para el análisis y la construcción de estructuras de datos (binario o textual). Se basa en el concepto de definición de datos estructuras de una manera declarativa, en lugar de código de procedimiento: más construcciones complejas se componen de una jerarquía de otras más simples. Es la primera biblioteca que hace que el análisis sea divertido, en lugar del dolor de cabeza habitual que es en la actualidad.

constructo es muy robusto y potente, y acaba de leer el tutorial le ayudará a comprender mejor el problema. El autor también tiene planes para autogenerar el código C a partir de las definiciones, por lo que definitivamente vale la pena el esfuerzo de leer sobre él.