Разбор пользовательских пакетов данных объектно-ориентированным способом - PullRequest
3 голосов
/ 21 апреля 2019

В настоящее время я занимаюсь разработкой программного обеспечения на C ++, куда я отправляю и получаю пользовательские пакеты данных. Я хочу анализировать и управлять этими пакетами хорошо структурированным способом. Очевидно, я сначала получаю заголовок, а после этого тело данных. Основная проблема заключается в том, что мне не нравится создавать объект-пакет только с информацией заголовка, а затем добавлять данные тела. Что такое элегантный способ анализа и хранения пользовательских пакетов данных?

Вот примерный пример того, как может выглядеть такой пользовательский пакет данных:

+-------+---------+---------+----------+------+
| Magic | Command | Options | Bodysize | Body |
+-------+---------+---------+----------+------+

(Предположим, магия составляет 4 байта, команда 1 байт, опции 2 байта, размер тела 4 байта, а само тело имеет переменную длину.) Как бы я проанализировал это без использования сторонних библиотек?

Обычно я бы сказал, что что-то подобное можно сделать для хранения пакетных данных:

#include <array>

class Packet {
public:

    explicit Packet(std::array<char, 10> headerbytes);

    void set_body(std::vector<char> data);
    std::vector<char> get_body();

    int8_t get_command();

    int16_t get_options();

    bool is_valid();

private:

    bool valid;

    int8_t _command;

    int16_t _options;

    int32_t body_size;

    std::vector<char> _data;

};

Проблема в том, что я сначала предоставляю информацию заголовка, а потом хакерским способом добавляю данные тела. У объекта пакета есть момент времени, когда он доступен в неполном состоянии.

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

Я занимаюсь разработкой на C ++, а для отправки и получения данных через сокеты используется библиотека boost.

Ответы [ 4 ]

1 голос
/ 25 апреля 2019

Вы можете использовать исключения, чтобы предотвратить создание неполных объектов пакета.

Я бы использовал указатели символов вместо векторов для производительности.

// not intended to be inherited
class Packet final {
public:
    Packet(const char* data, unsigned int data_len) {
        if(data_len < header_len) {
            throw std::invalid_argument("data too small");
        }

        const char* dataIter = data;

        if(!check_validity(dataIter)) {
            throw std::invalid_argument("invalid magic word");
        }
        dataIter += sizeof(magic);
        memcpy(&command, dataIter, sizeof(command)); // can use cast & assignment, too
        dataIter += sizeof(command);
        memcpy(&options, dataIter, sizeof(options)); // can use cast & assignment, too
        dataIter += sizeof(options);
        memcpy(&body_size, dataIter, sizeof(body_size)); // can use cast & assignment, too
        dataIter += sizeof(body_size);

        if( data_len < body_size+header_len) {
            throw std::invalid_argument("data body too small");
        }

        body = new char[body_size];
        memcpy(body, dataIter, body_size);
    }

    ~Packet() {
        delete[] body;
    }

    int8_t get_command() const {
        return command;
    }

    int16_t get_options() const {
        return options;
    }

    int32_t get_body_size() const {
        return body_size;
    }

    const char* get_body() const {
        return body;
    }

private:
    // assumes len enough, may add param in_len for robustness
    static bool check_validity(const char* in_magic) {
        return ( 0 == memcmp(magic, in_magic, sizeof(magic)) );
    }

    constexpr static char magic[] = {'a','b','c','d'};
    int8_t command;
    int16_t options;
    int32_t body_size;
    char* body;

    constexpr static unsigned int header_len = sizeof(magic) + sizeof(command)
            + sizeof(options) + sizeof(body_size);
};

Примечание: это мой первый пост вТак что, пожалуйста, дайте мне знать, если что-то не так с сообщением, спасибо.

1 голос
/ 24 апреля 2019

Для этого случая я бы использовал шаблон проектирования конвейера , создающий 3 класса процессора пакетов:

  • Команда (также обрабатывает магические байты)
  • Опции
  • Body (также обрабатывает размер тела)

все производные от одного базового класса.

typedef unsigned char byte;

namespace Packet
{
    namespace Processor
    {
        namespace Field
        {
            class Item
            {
            public:
                /// Returns true when the field was fully processed, false otherwise.
                virtual bool operator () (const byte*& begin, const byte* const end) = 0;
            };

            class Command: public Item
            {
            public:
                virtual bool operator () (const byte*& begin, const byte* const end);
            };

            class Options: public Item
            {
            public:
                virtual bool operator () (const byte*& begin, const byte* const end);
            };

            class Body: public Item
            {
            public:
                virtual bool operator () (const byte*& begin, const byte* const end);
            };
        }

        class Manager
        {
        public:
            /// Called every time new data is received
            void operator () (const byte* begin, const byte* const end)
            {
                while((*fields[index])(begin, end))
                {
                    incrementIndex();
                }
            }

        protected:
            void incrementIndex();

            Field::Command command;
            Field::Options options;
            Field::Body body;
            Field::Item* const fields[3] = { &command, &options, &body };
            byte index;
        };
    }
}
1 голос
/ 24 апреля 2019

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

struct Header {
  static constexpr SIZE=10;
  Header(std::array<char,SIZE>);

  std::int8_t get_command() const {return command;}
  std::int16_t get_options() const {return options;}
  std::int32_t body_size() const {return length;}

private:
  std::int8_t command;
  std::int16_t options;
  std::int32_t length;
};

struct Packet : private Header {
  using Body=std::vector<char>;
  Packet(const Header &h,Body b) : Header(h),body(std::move(b))
  {if(body.size()!=body_size()) throw …;}

  using Header::get_command;
  using Header::get_options;
  const Body& get_body() const {return body;}

private:
  Body body;
};

// For some suitable Stream class:
Header read1(Stream &s)
{return {s.read<Header::SIZE>()};}
Packet read2(const Header &h,Stream &s)
{return {h,s.read(h.body_size())};}
Packet read(Stream &s)
{return read2(read1(s),s);}

Обратите внимание, что частное наследование не позволяет неопределенному поведению удалять Packet через Header*, а также, безусловно, непреднамеренное

const Packet p=read(s);
const Packet q=read2(p,s);   // same header?!

Композиция, конечно, тоже будет работать, но может привести к большему количеству кода адаптера в полной реализации.

Если бы вы действительно оптимизировали, вы могли бы сделать HeaderOnly без размера тела и извлечь из него Header и Packet.

0 голосов
/ 25 апреля 2019

Полагаю, вы пытаетесь Объектно-ориентированные сети .Если это так, лучшим решением для такого анализа будет Flatbuffers или Cap'n Proto Генератор кода C ++.Определив схему, вы получите код конечного автомата, который будет анализировать пакеты эффективным и безопасным способом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...