Pregunta

Estoy leyendo un flujo de bytes que contiene una serie de descriptores de longitud variable que estoy representando como varias estructuras / clases en mi código. Cada descriptor tiene un encabezado de longitud fija en común con todos los otros descriptores, que se utilizan para identificar su tipo.

¿Existe un modelo o patrón apropiado que pueda usar para analizar y representar mejor cada descriptor y luego realizar una acción apropiada dependiendo de su tipo?

¿Fue útil?

Solución

He escrito muchos de estos tipos de analizador.

Te recomiendo que leas el encabezado de longitud fija y luego envíes al constructor correcto a tus estructuras usando un simple conmutador, pasando el encabezado fijo y el flujo al constructor para que pueda consumir la parte variable del flujo. .

Otros consejos

Este es un problema común en el análisis de archivos. Comúnmente, lees la parte conocida del descriptor (que en este caso es de longitud fija en este caso, pero no siempre), y la ramificas allí. En general, uso un patrón de estrategia aquí, ya que generalmente espero que el sistema sea ampliamente flexible, pero un interruptor recto o una fábrica pueden funcionar también.

La otra pregunta es: ¿controla y confía en el código posterior? Significado: ¿La implementación de fábrica / estrategia? Si lo hace, entonces solo puede darles el flujo y la cantidad de bytes que espera que consuman (quizás colocando algunas aserciones de depuración en su lugar, para verificar que do lean exactamente la cantidad correcta).

Si no confía en la implementación de fábrica / estrategia (tal vez permita que el código de usuario use deserializadores personalizados), entonces construiría un contenedor en la parte superior de la secuencia ( ejemplo: SubStream de protobuf-net ), que solo permite que se consuma el número esperado de bytes (luego se reporta EOF), y no permite operaciones de búsqueda / etc. fuera de este bloque. También tendría controles de tiempo de ejecución (incluso en versiones de lanzamiento) de que se han consumido suficientes datos, pero en este caso probablemente solo leería más allá de los datos no leídos, es decir, si esperábamos que el código de flujo descendente consumiera 20 bytes, pero solo leyera 12 , luego salta los próximos 8 y lee nuestro siguiente descriptor.

Para ampliar eso; Un diseño de estrategia aquí podría tener algo como:

interface ISerializer {
    object Deserialize(Stream source, int bytes);
    void Serialize(Stream destination, object value);
}

Puede crear un diccionario (o simplemente una lista si el número es pequeño) de dichos serializadores por marcadores esperados, y resolver su serializador, luego invocar el método Deserialize . Si no reconoce el marcador, entonces (uno de):

  • omite el número dado de bytes
  • lanzar un error
  • almacene los bytes adicionales en un búfer en algún lugar (permitiendo el viaje de ida y vuelta de datos inesperados)

Como una nota al respecto, este enfoque (estrategia) es útil si el sistema se determina en tiempo de ejecución, ya sea por reflexión o por medio de un DSL en tiempo de ejecución (etc.). Si el sistema es completamente predecible en tiempo de compilación (porque no cambia, o porque está usando la generación de código), entonces un enfoque directo de switch puede ser más apropiado - y probablemente no necesite ninguna interfaz adicional, ya que puede inyectar el código apropiado directamente.

Una cosa clave que debe recordar, si está leyendo de la secuencia y no detecta un encabezado / mensaje válido, deseche solo el primer byte antes de volver a intentarlo. Muchas veces he visto que se desecha un paquete o mensaje completo, lo que puede resultar en la pérdida de datos válidos.

Esto parece que podría ser un trabajo para el Factory Method o quizás Abstract Factory . En función del encabezado, elija a qué método de fábrica llamar, y eso devuelve un objeto del tipo relevante.

Si esto es mejor que simplemente agregar constructores a una declaración de cambio depende de la complejidad y la uniformidad de los objetos que está creando.

sugeriría:

fifo = Fifo.new

while(fd is readable) {
  read everything off the fd and stick it into fifo
  if (the front of the fifo is has a valid header and 
      the fifo is big enough for payload) {

      dispatch constructor, remove bytes from fifo
  }
}

Con este método:

  • puede hacer un error al revisar las cargas útiles y potencialmente desechar datos erróneos
  • los datos no esperan en el búfer de lectura del fd (puede ser un problema para grandes cargas útiles)

Si desea que sea agradable OO, puede usar el patrón de visitante en una jerarquía de objetos. La forma en que lo hice fue así (para identificar los paquetes capturados fuera de la red, casi lo mismo que podría necesitar):

  • enorme jerarquía de objetos, con una clase padre

  • cada clase tiene un controlador estático que se registra con su padre, por lo que el padre sabe acerca de sus hijos directos (esto fue c ++, creo que este paso no es necesario en idiomas con un buen soporte de reflexión)

  • cada clase tenía un método constructor estático que obtuvo la parte restante de la secuencia de bytes y, en base a eso, decidió si es su responsabilidad manejar esos datos o no

  • Cuando llegó un paquete, simplemente lo pasé al método constructor estático de la clase principal (llamado Paquete), que a su vez verificó a todos sus hijos si es su responsabilidad manejar ese paquete, y esto fue recursivo, hasta que una clase en la parte inferior de la jerarquía devolvió la clase instanciada.

  • Cada uno del estático " constructor " los métodos cortan su propio encabezado de la secuencia de bytes y transmiten solo la carga útil a sus hijos.

La ventaja de este enfoque es que puede agregar nuevos tipos en cualquier lugar de la jerarquía de objetos SIN que necesiten ver / cambiar CUALQUIER otra clase. Funcionó notablemente bien y bien para los paquetes; fue así:

  • paquete
  • EthernetPacket
  • IPPacket
  • UDPPacket, TCPPacket, ICMPPacket
  • ...

Espero que puedas ver la idea.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top