Domanda

Sto leggendo da un flusso di byte che contiene una serie di descrittori di lunghezza variabile che sto rappresentando come varie strutture / classi nel mio codice. Ogni descrittore ha un'intestazione a lunghezza fissa in comune con tutti gli altri descrittori, che vengono utilizzati per identificarne il tipo.

Esiste un modello o modello appropriato che posso usare per analizzare e rappresentare al meglio ogni descrittore e quindi eseguire un'azione appropriata a seconda del tipo?

È stato utile?

Soluzione

Ho scritto molti di questi tipi di parser.

Ti consiglio di leggere l'intestazione a lunghezza fissa e quindi di inviarla al costruttore corretto alle tue strutture usando una semplice custodia, passando l'intestazione fissa e il flusso a quel costruttore in modo che possa consumare la parte variabile del flusso .

Altri suggerimenti

Questo è un problema comune nell'analisi dei file. Comunemente, leggi la parte conosciuta del descrittore (che fortunatamente è a lunghezza fissa in questo caso, ma non è sempre), e dirama lì. In genere utilizzo qui un modello di strategia , poiché generalmente mi aspetto che il sistema sia ampiamente flessibile - ma anche un interruttore o una fabbrica possono funzionare.

L'altra domanda è: controlli e ti fidi del codice downstream? Significato: l'implementazione di fabbrica / strategia? Se lo fai, allora puoi semplicemente dare loro il flusso e il numero di byte che ti aspetti di consumare (magari mettendo in atto alcune asserzioni di debug, per verificare che faccia leggano esattamente la giusta quantità).

Se non puoi fidarti dell'implementazione di fabbrica / strategia (forse permetti al codice utente di usare deserializzatori personalizzati), allora costruirò un wrapper in cima allo stream ( esempio: SubStream da protobuf-net ), che consente di consumare solo il numero previsto di byte (riportando in seguito EOF) e non consente operazioni di ricerca / ecc. al di fuori di questo blocco. Avrei anche dei controlli di runtime (anche nelle build di rilascio) che sono stati consumati abbastanza dati - ma in questo caso probabilmente leggerei semplicemente qualsiasi dato non letto - vale a dire se ci aspettassimo che il codice downstream consumasse 20 byte, ma leggesse solo 12 , quindi salta il prossimo 8 e leggi il nostro prossimo descrittore.

Per espandere su quello; un progetto di strategia qui potrebbe avere qualcosa del tipo:

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

È possibile creare un dizionario (o solo un elenco se il numero è piccolo) di tali serializzatori per marcatori previsti e risolvere il serializzatore, quindi invocare il metodo Deserialize . Se non riconosci il marker, allora (uno di):

  • salta il dato numero di byte
  • genera un errore
  • memorizza i byte extra in un buffer da qualche parte (consentendo il round-trip di dati imprevisti)

Come nota a margine di quanto sopra - questo approccio (strategia) è utile se il sistema è determinato in fase di esecuzione, tramite reflection o tramite un DSL di runtime (ecc.). Se il sistema è interamente prevedibile in fase di compilazione (perché non cambia o perché stai utilizzando la generazione di codice), allora un approccio diretto switch potrebbe essere più appropriato - e probabilmente non hai bisogno di interfacce extra, dato che puoi iniettare direttamente il codice appropriato.

Una cosa fondamentale da ricordare, se stai leggendo dallo stream e non rilevi un'intestazione / messaggio validi, elimina solo il primo byte prima di riprovare. Molte volte ho visto un intero pacchetto o messaggio essere invece eliminato, il che può comportare la perdita di dati validi.

Sembra che potrebbe essere un lavoro per il Metodo di fabbrica o forse Fabbrica astratta . In base all'intestazione scegli quale metodo di fabbrica chiamare e che restituisce un oggetto del tipo pertinente.

Il fatto che sia meglio che aggiungere semplicemente costruttori a un'istruzione switch dipende dalla complessità e dall'uniformità degli oggetti che stai creando.

Suggerirei:

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 questo metodo:

  • puoi fare qualche controllo degli errori per payload errati e potenzialmente gettare via i dati cattivi
  • i dati non sono in attesa sul buffer di lettura di fd (può essere un problema per payload di grandi dimensioni)

Se desideri che sia OO piacevole, puoi utilizzare il modello visitatore in una gerarchia di oggetti. Come ho fatto è stato così (per identificare i pacchetti catturati dalla rete, praticamente la stessa cosa di cui potresti aver bisogno):

  • enorme gerarchia di oggetti, con una classe genitore

  • ogni classe ha un regolatore statico che si registra con il suo genitore, quindi il genitore conosce i suoi figli diretti (questo era c ++, penso che questo passaggio non sia necessario nei linguaggi con un buon supporto di riflessione)

  • ogni classe aveva un metodo di costruzione statico che ha ottenuto la parte rimanente del bytestream e sulla base di ciò, ha deciso se è sua responsabilità gestire tali dati o meno

  • Quando è arrivato un pacchetto, l'ho semplicemente passato al metodo del costruttore statico della classe principale principale (chiamato pacchetto), che a sua volta ha controllato tutti i suoi figli se è loro responsabilità gestire quel pacchetto, e questo è andato in modo ricorsivo, fino a quando una classe in fondo alla gerarchia non ha restituito la classe istanziata.

  • Ciascuno del costruttore "quotato" statico metodi ha tagliato la propria intestazione dal bytestream e ha trasmesso solo il payload ai suoi figli.

Il lato positivo di questo approccio è che puoi aggiungere nuovi tipi in qualsiasi punto della gerarchia degli oggetti SENZA per vedere / cambiare QUALSIASI altra classe. Funzionava straordinariamente bene e bene per i pacchetti; è andata così:

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

Spero che tu possa vedere l'idea.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top