Frage

Ich lese aus einem Byte -Stream, der eine Reihe von Deskriptoren der variablen Länge enthält, die ich als verschiedene Strukturen/Klassen in meinem Code darstelle. Jeder Deskriptor verfügt über eine feste Länge -Headerin mit allen anderen Deskriptoren, die verwendet werden, um seinen Typ zu identifizieren.

Gibt es ein geeignetes Modell oder Muster, mit dem ich jeden Deskriptor am besten analysieren und darstellen kann, und dann je nach Typ eine geeignete Aktion ausführen?

War es hilfreich?

Lösung

Ich habe viele dieser Arten von Parser geschrieben.

Ich empfehle Ihnen, den Header mit fester Länge zu lesen und dann mit einem einfachen Switch-Fall an den richtigen Konstruktor an Ihre Strukturen zu senden, den festen Header und den Stream an diesen Konstruktor weiterzugeben, damit der variable Teil des Streams konsumiert werden kann.

Andere Tipps

Dies ist ein häufiges Problem bei der Analyse der Datei. Im Allgemeinen lesen Sie die bekannt Ein Teil des Deskriptors (der glücklicherweise in diesem Fall festgelegt ist, aber nicht immer), und ihn dort verzweigen. Im Allgemeinen benutze ich a Strategiemuster Hier, da ich im Allgemeinen erwarte, dass das System weitgehend flexibel ist - kann auch ein gerader Schalter oder eine Fabrik funktionieren.

Die andere Frage ist: Kontrolle und vertrauen Sie dem nachgeschalteten Code? Bedeutung: Die Fabrik- / Strategie -Implementierung? Wenn Sie dies tun, können Sie ihnen einfach den Stream und die Anzahl der Bytes geben, von denen Sie erwarten tun Lesen Sie genau die richtige Menge).

Wenn du kippen Vertrauen Sie der Werks-/Strategie-Implementierung (möglicherweise erlauben Sie dem Benutzercode, benutzerdefinierte Deserializer zu verwenden). Dann würde ich einen Wrapper über dem Stream konstruieren ((Beispiel: SubStream Aus Protobuf-Net), damit nur die erwartete Anzahl von Bytes konsumiert werden (danach von EOF berichten) und nicht zulässt, dass Such-/ETC -Vorgänge außerhalb dieses Blocks. Ich hätte auch Laufzeitüberprüfungen (selbst in Release -Builds), dass genügend Daten verbraucht wurden - aber in diesem Fall würde ich wahrscheinlich nur ungelesene Daten vorbei lesen - dh wenn wir erwarten, dass der nachgeschaltete Code 20 Bytes verbraucht, aber nur 12 lautete 12 Überspringen Sie dann die nächsten 8 und lesen Sie unseren nächsten Deskriptor.

Das erweitern; Ein Strategiedesign hier könnte so etwas haben wie:

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

Sie können ein Wörterbuch (oder nur eine Liste, wenn die Anzahl klein ist) dieser Serialisierer pro erwarteten Markierungen erstellen und Ihren Serializer lösen und dann die aufrufen Deserialize Methode. Wenn Sie den Marker nicht erkennen, dann (eines von):

  • Überspringen Sie die angegebene Anzahl von Bytes
  • einen Fehler werfen
  • Speichern Sie die zusätzlichen Bytes irgendwo in einem Puffer (ermöglichen die Rundreise für unerwartete Daten)

Als Seitenstart des oben genannten - ist dieser Ansatz (Strategie) nützlich, wenn das System zur Laufzeit entweder über die Reflexion oder über eine Laufzeit -DSL (usw.) bestimmt wird. Wenn das System ist völlig Vorhersehbar bei der Kompilierungszeit (weil es sich nicht ändert oder weil Sie die Code-Generation verwenden), dann eine Gerade switch Der Ansatz ist möglicherweise angemessener - und Sie benötigen wahrscheinlich keine zusätzlichen Schnittstellen, da Sie den entsprechenden Code direkt injizieren können.

Eine wichtige Sache, an die Sie sich erinnern sollten, wenn Sie aus dem Stream lesen und keinen gültigen Header/eine gültige Meldung erkennen, werfen Sie nur das erste Byte weg, bevor Sie es erneut versuchen. Oft habe ich stattdessen ein ganzes Paket oder eine Nachricht weggeworfen, was dazu führen kann, dass gültige Daten verloren gehen.

Das klingt so, als wäre es ein Job für die Fabrikmethode oder vielleicht Zusammenfassung Fabrik. Basierend auf dem Header wählen Sie, welche Fabrikmethode Sie aufrufen sollen, und die ein Objekt des relevanten Typs zurückgibt.

Ob dies besser ist, als einfach Konstruktoren zu einer Switch -Anweisung hinzuzufügen, hängt von der Komplexität und der Einheitlichkeit der von Ihnen erstellten Objekte ab.

Ich würde vorschlagen:

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

Mit dieser Methode:

  • Sie können einige Fehlerprüfung für schlechte Nutzlasten durchführen und möglicherweise schlechte Daten wegwerfen
  • Daten warten nicht auf den Lesepuffer des FD (können ein Problem für große Nutzlasten sein)

Wenn Sie möchten, dass es schön ist, können Sie das Besuchermuster in einer Objekthierarchie verwenden. Wie ich es gemacht habe, war so (zum Identifizieren von Paketen, die das Netzwerk erfasst haben, so ziemlich das Gleiche, was Sie brauchen):

  • riesige Objekthierarchie mit einer übergeordneten Klasse

  • Jede Klasse hat einen statischen Conductor, der sich mit ihrem Elternteil registriert, sodass der Elternteil über seine direkten Kinder weiß (dies war C ++, ich denke, dieser Schritt wird in Sprachen mit guter Reflexionsunterstützung nicht benötigt).

  • Jede Klasse hatte eine statische Konstruktor -Methode, die den verbleibenden Teil des Bytestreams erhielt, und basierend darauf entschied sie, ob es in seiner Verantwortung liegt, diese Daten zu behandeln oder nicht

  • Als ein Paket hereinkam, habe ich es einfach an die statische Konstruktor -Methode der Hauptklasse der Hauptklasse (als Paket bezeichnet) weitergegeben, was wiederum alle ihre Kinder überprüfte, wenn es ihre Verantwortung für die Verantwortung dieses Pakets überprüfte, und dies ging rekursiv bis zu einem rekursiv Die Klasse am Ende der Hierarchie gab die sofortige Klasse zurück.

  • Jede der statischen "Konstruktor" -Methoden schnitt seinen eigenen Kopfball aus dem Bytestream und gab nur die Nutzlast an seine Kinder weiter.

Der Aufwärtstrend dieses Ansatzes ist, dass Sie überall in der Objekthierarchie neue Typen hinzufügen können OHNE müssen sehen/ändern IRGENDEIN Andere Klasse. Es funktionierte bemerkenswert schön und gut für Pakete; Es ging so:

  • Paket
  • Ethernetpacket
  • IPPacket
  • Udppacket, tcppacket, icmPpacket
  • ...

Ich hoffe, Sie können die Idee sehen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top