Разбор дескрипторов переменной длины из байтового потока и работа с их типом - PullRequest
2 голосов
/ 23 октября 2009

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

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

Ответы [ 6 ]

9 голосов
/ 23 октября 2009

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

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

2 голосов
/ 03 ноября 2009

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

2 голосов
/ 01 ноября 2009

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

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

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

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

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

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

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

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

1 голос
/ 31 октября 2009

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

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

0 голосов
/ 05 ноября 2009

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

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

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

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

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

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

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

  • Пакет
  • EthernetPacket
  • IPPacket
  • UDPPacket, TCPPacket, ICMPPacket
  • ...

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

0 голосов
/ 03 ноября 2009

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


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 (может быть проблемой для больших полезных нагрузок)
...