Анализ дескрипторов переменной длины из потока байтов и действие в зависимости от их типа

StackOverflow https://stackoverflow.com/questions/1612918

Вопрос

Я считываю данные из потока байтов, который содержит ряд дескрипторов переменной длины, которые я представляю в виде различных структур / классов в своем коде.Каждый дескриптор имеет заголовок фиксированной длины, общий со всеми остальными дескрипторами, которые используются для идентификации его типа.

Есть ли подходящая модель или шаблон, которые я могу использовать для наилучшего анализа и представления каждого дескриптора, а затем выполнить соответствующее действие в зависимости от его типа?

Это было полезно?

Решение

Я написал много таких парсеров.

Я рекомендую вам прочитать заголовок фиксированной длины, а затем отправить его правильному конструктору в ваши структуры, используя простой случай переключения, передав фиксированный заголовок и поток этому конструктору, чтобы он мог использовать переменную часть потока .

Другие советы

Это распространенная проблема при синтаксическом анализе файлов.Как правило, вы читаете известный часть дескриптора (который, к счастью, в данном случае имеет фиксированную длину, но не всегда) и разветвляйте его там.Обычно я использую шаблон стратегии здесь, поскольку я обычно ожидаю, что система будет в целом гибкой, но может работать и прямой коммутатор или заводская установка.

Другой вопрос заключается в следующем:контролируете ли вы нижестоящий код и доверяете ли ему?Значение:фабрика / реализация стратегии?Если вы это сделаете, то вы можете просто предоставить им поток и количество байтов, которые, как вы ожидаете, они будут потреблять (возможно, установив некоторые утверждения отладки, чтобы убедиться, что они делай прочитайте точно нужное количество).

Если вы не могу доверяйте реализации factory / strategy (возможно, вы разрешаете пользовательскому коду использовать пользовательские десериализаторы), тогда я бы создал оболочку поверх потока (пример: SubStream из protobuf-net), который позволяет использовать только ожидаемое количество байтов (впоследствии сообщая EOF) и не разрешает операции поиска / etc за пределами этого блока.У меня также были бы проверки во время выполнения (даже в сборках релизов), что было израсходовано достаточное количество данных, но в этом случае я бы, вероятно, просто прочитал мимо любых непрочитанных данных, т. Е.если мы ожидали, что нисходящий код займет 20 байт, но он прочитал только 12, то пропустите следующие 8 и прочитайте наш следующий дескриптор.

Чтобы подробнее остановиться на этом;один из вариантов стратегии здесь может иметь что-то вроде:

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

Вы могли бы создать словарь (или просто список, если число невелико) таких сериализаторов для ожидаемых маркеров и разрешить свой сериализатор, затем вызвать Deserialize способ.Если вы не распознаете маркер, то (один из):

  • пропустить заданное количество байтов
  • выдает ошибку
  • сохраните лишние байты где-нибудь в буфере (с учетом обратной передачи неожиданных данных).

В качестве дополнительного примечания к вышесказанному - этот подход (стратегия) полезен, если система определяется во время выполнения либо с помощью отражения, либо с помощью runtime DSL (etc).Если система является полностью предсказуемый во время компиляции (потому что он не меняется или потому что вы используете генерацию кода), тогда прямой switch подход может быть более подходящим - и вам, вероятно, не нужны никакие дополнительные интерфейсы, поскольку вы можете ввести соответствующий код напрямую.

Следует помнить одну важную вещь: если вы читаете из потока и не обнаруживаете допустимый заголовок / сообщение, отбросьте только первый байт, прежде чем пытаться снова. Много раз я видел, как вместо этого отбрасывался целый пакет или сообщение, что может привести к потере правильных данных.

Похоже, это может быть работа для фабричного метода или, возможно, Абстрактная фабрика . На основе заголовка вы выбираете, какой метод фабрики вызывать, и который возвращает объект соответствующего типа.

Лучше ли это, чем просто добавлять конструкторы в оператор switch, зависит от сложности и однородности создаваемых вами объектов.

Я бы предложил:

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

С помощью этого метода:

  • вы можете выполнить некоторую проверку ошибок на наличие плохих полезных нагрузок и, возможно, выбросить неверные данные
  • данные не ожидаются в буфере чтения fd (может быть проблемой для больших полезных нагрузок).

Если вы хотите, чтобы это был приятный OO, вы можете использовать шаблон visitor в иерархии объектов.Как я это сделал, было примерно так (для идентификации пакетов, захваченных вне сети, практически то же самое, что вам может понадобиться):

  • огромная иерархия объектов с одним родительским классом

  • каждый класс имеет статический конструктор, который регистрируется у своего родителя, поэтому родитель знает о своих прямых дочерних элементах (это был c ++, я думаю, что этот шаг не нужен в языках с хорошей поддержкой отражения)

  • у каждого класса был статический метод конструктора, который получал оставшуюся часть байтового потока и на основе этого решал, является ли его обязанностью обрабатывать эти данные или нет

  • Когда приходил пакет, я просто передавал его методу статического конструктора основного родительского класса (называемого Packet ), который, в свою очередь, проверял всех своих дочерних элементов, отвечают ли они за обработку этого пакета, и это происходило рекурсивно, пока один класс в нижней части иерархии не возвращал созданный класс обратно.

  • Каждый из статических методов "конструктора" вырезает свой собственный заголовок из байтового потока и передает только полезную нагрузку своим дочерним элементам.

Положительным моментом такого подхода является то, что вы можете добавлять новые типы в любом месте иерархии объектов БЕЗ необходимость увидеть / изменить Любой другой класс.Это работало на удивление хорошо для пакетов;все прошло примерно так:

  • Пакет
  • Пакет EthernetPacket
  • IPPacket - пакет
  • UDPPacket, TCPPacket, icmpppacket
  • ...

Я надеюсь, вы понимаете эту идею.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top