¿Cómo interpretar datos binarios en C++?
Pregunta
Estoy enviando y recibiendo datos binarios hacia/desde un dispositivo en paquetes (64 bytes).Los datos tienen un formato específico, partes del cual varían según la diferente solicitud/respuesta.
Ahora estoy diseñando un intérprete para los datos recibidos.Simplemente leer los datos por posiciones está bien, pero no parece tan interesante 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 irá con el relleno.
¿Quizás haya una mejor manera?
Relacionado:
Solución
He hecho esto innumerables veces antes:Es un escenario muy común.Hay una serie de cosas que prácticamente siempre hago.
No se preocupe demasiado por convertirlo en lo más eficiente disponible.
Si terminamos pasando mucho tiempo empacando y desempaquetando paquetes, siempre podemos cambiarlo para que sea más eficiente.Aunque todavía no he encontrado ningún caso en el que haya tenido que hacerlo, ¡no he implementado enrutadores de red!
Si bien el uso de estructuras/uniones es el enfoque más eficiente en términos de tiempo de ejecución, conlleva una serie de complicaciones:convencer a su compilador para que empaquete las estructuras/uniones para que coincidan con la estructura de octetos de los paquetes que necesita, trabaje para evitar problemas de alineación y endianidad, y una falta de seguridad, ya que hay poca o ninguna oportunidad de realizar comprobaciones de cordura en las compilaciones de depuración.
A menudo termino con una arquitectura que incluye los siguientes tipos de cosas:
- Una clase base de paquete.Todos los campos de datos comunes son accesibles (pero no modificables).Si los datos no se almacenan en un formato empaquetado, existe 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 usamos una función de empaquetado, entonces cada clase de presentación debe implementarla.
- Todo lo que pueda inferirse del tipo específico de clase de presentación (es decir,una identificación de tipo de paquete de un campo de datos común), se trata como parte de la inicialización y, por lo demás, no es modificable.
- Cada clase de presentación se puede construir a partir de un paquete desempaquetado o fallará si los datos del paquete no son válidos para ese tipo.Luego, esto se puede empaquetar en una fábrica para mayor comodidad.
- Si no tenemos RTTI disponible, podemos obtener "RTTI del pobre" usando la identificación del paquete para determinar qué clase de presentación específica es realmente un objeto.
En todo esto, es posible (aunque solo sea para compilaciones de depuración) verificar que cada campo modificable se establezca en un valor sensato.Si bien puede parecer mucho trabajo, hace que sea muy difícil tener un paquete con formato no válido, el contenido de un paquete preempaquetado se puede verificar fácilmente a simple vista usando un depurador (ya que todo está en variables de formato nativas normales de la plataforma).
Si tenemos que implementar un esquema de almacenamiento más eficiente, eso también puede incluirse en esta abstracción con un pequeño costo de rendimiento adicional.
Otros consejos
Es necesario utilizar estructuras y o uniones. Tendrá que asegurarse de que sus datos están correctamente embalado en ambos lados de la conexión y es posible que desee traducir desde y hacia la red de orden de bytes en cada extremo si hay alguna posibilidad de que ambos lados de la conexión podría estar en funcionamiento con una diferente 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, cuando la asignación:
packetBuffer.packetID = htonl(123456);
y luego cuando se recibe:
packetBuffer.packetID = ntohl(packetBuffer.packetID);
Estas son algunas discusiones de Endianness y alineación y estructura de embalaje
Si no empaqueta la estructura que va a terminar alineado con los límites de palabra y la disposición interna de la estructura y su tamaño será incorrecto.
Es difícil decir cuál es la mejor solución es sin conocer el formato exacto (s) de los datos. Ha considerado el uso sindicatos?
Estoy de acuerdo con Wuggy. También puede utilizar la generación de código para hacer esto. Utilizar un simple archivo de definición de datos para definir todos los tipos de paquetes, a continuación, ejecute un script en Python sobre ella para generar estructuras de prototipos y / serialiation funciones unserialization para cada uno.
Esta es una solución "out-of-the-box", pero me gustaría sugerir a echar un vistazo a la Python construir biblioteca.
Construct es una biblioteca de Python para el análisis y la construcción de los datos estructuras (binarios o de texto). Está basado en el concepto de datos que definen estructuras de una manera declarativa, en lugar de código de procedimiento: más constructos complejos se componen de una jerarquía de los más simples. Es el primera biblioteca que se burla de análisis, en lugar de la habitual es el dolor de cabeza hoy en día.
construcción es muy robusta y de gran alcance, y sólo leer el tutorial le ayudará a comprender mejor el problema. El autor también tiene planes para el código de generación automática de las definiciones C, por lo que definitivamente vale la pena el esfuerzo de leer.