Question

Je lis un flux d'octets contenant une série de descripteurs de longueur variable que je représente sous différentes structures / classes dans mon code. Chaque descripteur a un en-tête de longueur fixe en commun avec tous les autres descripteurs utilisés pour identifier son type.

Existe-t-il un modèle ou un modèle approprié que je peux utiliser pour mieux analyser et représenter chaque descripteur, puis effectuer une action appropriée en fonction de son type?

Était-ce utile?

La solution

J'ai écrit beaucoup de ces types d'analyseurs.

Je vous recommande de lire l'en-tête de longueur fixe, puis de l'envoyer au constructeur approprié vers vos structures à l'aide d'un simple cas de commutation, en passant l'en-tête et le flux fixes à ce constructeur afin qu'il puisse utiliser la partie variable du flux. .

Autres conseils

Il s'agit d'un problème courant dans l'analyse de fichier. Généralement, vous lisez la partie connue du descripteur (qui heureusement est de longueur fixe dans ce cas, mais ce n’est pas toujours le cas), et vous la branchez là. En général, j'utilise un modèle de stratégie ici, car je m'attends généralement à ce que le système soit globalement flexible - mais un commutateur droit ou une usine peut également fonctionner.

L’autre question est la suivante: contrôlez-vous et faites-vous confiance au code en aval? Signification: la mise en œuvre usine / stratégie? Si vous le faites, vous pouvez simplement leur donner le flux et le nombre d'octets que vous vous attendez à ce qu'ils consomment (peut-être en mettant en place des assertions de débogage pour vérifier qu'ils font lire exactement la bonne quantité).

Si vous ne pouvez pas faire confiance à la mise en œuvre stratégie / usine (vous autorisez peut-être le code utilisateur à utiliser des désensionneurs personnalisés), je construirais un wrapper par-dessus le flux ( exemple: Sous-flux à partir de protobuf-net ), qui autorise uniquement la consommation du nombre d'octets prévu (en indiquant EOF par la suite) et n'autorise pas les opérations de recherche / etc en dehors de ce bloc. J'aurais aussi des vérifications à l'exécution (même dans les versions) que suffisamment de données ont été consommées - mais dans ce cas, je lirais probablement toutes les données non lues - c'est-à-dire si nous nous attendions à ce que le code en aval consomme 20 octets, mais il ne lit que 12 , passez ensuite les 8 suivants et lisez notre descripteur suivant.

Pour développer cela; une conception de stratégie ici pourrait avoir quelque chose comme:

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

Vous pouvez créer un dictionnaire (ou simplement une liste si le nombre est petit) de ces sérialiseurs par marqueurs attendus et résoudre votre sérialiseur, puis invoquer la méthode Deserialize . Si vous ne reconnaissez pas le marqueur, alors (un des):

  • ignorer le nombre d'octets donné
  • émet une erreur
  • stocke les octets supplémentaires dans une mémoire tampon quelque part (permettant l’aller-retour de données inattendues)

En marge de ce qui précède, cette approche (stratégie) est utile si le système est déterminé au moment de l'exécution, soit par réflexion, soit via un DSL d'exécution (etc.). Si le système est entièrement prévisible au moment de la compilation (parce qu'il ne change pas ou que vous utilisez la génération de code), une approche directe commutateur peut être plus efficace. approprié - et vous n’avez probablement pas besoin d’interfaces supplémentaires, car vous pouvez injecter directement le code approprié.

Une chose clé à retenir, si vous lisez dans le flux et ne détectez pas un en-tête / message valide, jetez uniquement le premier octet avant de réessayer. Plusieurs fois, j'ai vu un paquet ou un message entier être jeté à la place, ce qui peut entraîner la perte de données valides.

Cela semble être un travail pour la Méthode d'usine ou peut-être Fabrique abstraite . En fonction de l’en-tête, vous choisissez la méthode d’usine à appeler et renvoie un objet du type concerné.

Que ce soit mieux que d'ajouter simplement des constructeurs à une instruction switch dépend de la complexité et de l'uniformité des objets que vous créez.

Je suggérerais:

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

Avec cette méthode:

  • vous pouvez rechercher des erreurs dans les charges utiles et éventuellement jeter des données erronées
  • les données ne sont pas en attente dans le tampon de lecture du disque dur (cela peut poser problème pour les charges utiles volumineuses)

Si vous souhaitez que ce soit un bon OO, vous pouvez utiliser le modèle de visiteur dans une hiérarchie d'objets. Voici comment je l'ai fait (pour identifier les paquets capturés sur le réseau, vous aurez peut-être besoin de la même chose):

  • hiérarchie d'objet énorme, avec une classe parente

  • chaque classe a un constructeur statique qui s'enregistre auprès de son parent afin que le parent connaisse ses enfants directs (c ++, je pense que cette étape n'est pas nécessaire dans les langages avec un bon support de réflexion)

  • chaque classe avait une méthode de constructeur statique qui récupérait la partie restante du flux bytestream et, sur cette base, il décidait s'il était de sa responsabilité de gérer ces données ou non

  • Quand un paquet est arrivé, je l'ai simplement passé à la méthode du constructeur statique de la classe parent principale (appelée Packet), qui à son tour a vérifié tous ses enfants s'il était de leur responsabilité de gérer ce paquet, et cela allait récursivement, jusqu'à ce qu'une classe au bas de la hiérarchie retourne la classe instanciée.

  • Chacun des statiques "constructeur" Les méthodes coupent leur propre en-tête dans le flux bytestream et ne transmettent que la charge à ses enfants.

L’avantage de cette approche est que vous pouvez ajouter de nouveaux types n’importe où dans la hiérarchie des objets SANS avoir besoin de voir / modifier ANY d’une autre classe. Cela a fonctionné remarquablement bien et bien pour les paquets; ça s'est passé comme ça:

  • Paquet
  • EthernetPacket
  • IPPacket
  • Paquet UDP, TCPPacket, ICMPPacket
  • ...

J'espère que vous pourrez voir l'idée.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top