Pull to refresh

Comments 16

А почему большой switch? По-моему, таблица была бы гораздо эффективнее. Заодно можно было бы поименовать сообщения не по id, а с осмысленными именами, а еще лучше по самому имени класса-приемника.
Я не понял как именно вы хотите заменить id на строки в самом пакете сообщения. В начале сообщения всегда идет константное количество байтов для записи id, которое однозначно определяет то, что пойдет дальше. Можно заменить на строку постоянной длины, но она опять таки должна быть как можно меньше по размерам, чтобы не было большого overhead'а по памяти, так почему просто не использовать uint для этого?

В большинстве случаев сериализатору (это все сериализаторы, которые я видел) нужно знать о типе класса сообщения еще на этапе компиляции. Допустим у нас есть таблица, которая переводит название сообщения в его id, который пойдет в поток на отправку, и наоборот. Как с помощью этой таблицы вызвать сериализатор с нужным типом? Я вижу тут 2 пути: написание опять таки switch и использование перегрузок функций с шаблоном Int2Type (Подсмотрел в книге Александреску «Современное программирование на С++»). Switch будет работать быстрее, а с помощью автоматической генерации мы получаем и скорость и удобство.
Мне просто подумалось, что
<source lang="cpp"> mixin ProtocolPool!(int, BinaryBackend, 0, AMsg, 1, BMsg, 2, CMsg ); </source>
Можно было бы заменить на
<source lang="cpp"> mixin ProtocolPool!(int, BinaryBackend, "AMsg", "BMsg", "CMsg"); </source>
С автоматической генерацией id по именам классов. Если id-строки не подойдут, то можно генерировать его по порядку от 0 до N или же как целочисленную функцию строки.
Ну все, сдаюсь, я не понимаю, как парсер работает…
А, понял. Да, можно сделать вот так:
mixin ProtocolPool!(int, BinaryBackend, AMsg, BMsg, CMsg); 


Но тут возникает подводный камень, так как индексы для сообщения вычисляются сами. Очень возможна ситуация, когда программист случайно перепутает местами сообщения в списке, и все… все ломается. Поэтому я решил специально заставлять явно указывать индексы, чтобы можно было проверить одинаковы ли протоколы на клиенте и сервере.
То есть вы решили, что программист вероятнее перепутает позицию типа, чем число?
Вообще, я тоже об этом думал, потому и предложил изначально генерировать строковый id. Оверхед не такой уж и значительный, зато точно ничего никто не перепутает. Передавать строку как {len: uint, data: char[]} и все ОК, тем более, что типичные имена не такие уж и большие.
Ну можно использовать строки как id, главное следить за своими именами, чтобы не раздувать head сообщения. Сейчас меня посетила мысль, что можно просто брать хеш от имени и это будет уникальный id, который не зависит от порядка перечисления сообщений.
Да, я об этом тоже думал, только ведь коллизии. Как думаете решать этот вопрос?
Тут будет много подводных камней. Можно обнаружить коллизию на этапе компиляции и добавить к имени, например, последний символ и опять взять кеш. Главное продумать ситуацию, когда есть два разных клиента с немного разными наборами сообщений, а сервер имеет самый полный набор. Тогда обойти коллизии нереально…
Я не знаю D. Но применительно к C++, как бы можно решить эту же задачу?
Я вижу так:
С посылкой сообщения все довольно понятно, просто и красиво.
А вот как быть с приемом сообщения.
Мы получили строку(массив байт), в нем содержится тип и определенная структура. Эта структура у разных сообщения, очевидно, может быть совершенно разной. Как мы можем вызовом одной функции вернуть разные объекты? Вижу только одно решение, возвращать указатель на базовый класс, а в реальности там будет находится объект дочернего класса. Но тогда в базовом классе нужно для каждого типа сообщения создавать свой метод для чтения определенной структуры. Или же использоваться dynamic_cast от базового к дочернему. Первый способ — жесть, второй — мне не нравится, считаю, что dynamic_cast — это плохо. Как бы вы поступили?
В предыдущем проекте на С++ я решал эту проблему по первому варианту. У каждого сообщения была своя реализация чтения из потока байтов, а в менеджере была map<int, AbstractMessage*>. Хотя я как можно упростил создание реализаций методов чтения, такой вариант меня не радовал. И простым для использования нельзя назвать и эффективным тоже.
Тут на самом деле большая проблема с сериализатором. У меня так и не получилось создать удобный сериализатор (я про C++), который не требовал бы вмешательства в классы (макросы или регистрации полей). Если допустить, что имеется общий алгоритм для чтения классов из байтов, то копать нужно в сторону шаблонов для автоматического построения иерархий классов, на каждом уровне проверять id на совпадение с заданным на этапе компиляции, и при совпадении вызывать сериализатор с нужным типом.
Насколько я понял, нашу проблему он не решает. Сериализация/десериализация это не проблема. Тут проблема стоит в построении гибкой архитектуры управления сетевыми сообщениями. Вероятно, есть все же красивое решение, но мы пока о нем не знаем.
Понятно.
Я сейчас решаю похожим образом, но не исключено, что более плохим.
class Message
{
  enum MessageType
  {
    ..
  };
  
  Message(MessageType type);
  void add_param(..);
  void add_param(..);
  void get_param(..);
  void get_param(..);
  
  MessageType get_message_tyoe();
};


Да, не совсем безопасно, ошибки в compile-time не поймаешь. Зато добавление методов лишь в одном классе и никаких dynamic_cast.

Кстати от switch в итоге все равно ведь не избавишься. Можно(и нужно) лишь заменить его на словарик, ТипСообщения => ФункцияОбработки. Ведь нам не просто нужно получить сообщение, а еще каким-то образом его в дальнейшем использовать(сохранить/отобразить/..).
Sign up to leave a comment.

Articles