Диспетчер довільних повідомлень на базі google protocol buffers - kildekode
Пісочниця →
З'явився вільний день, і я вирішив погратися з бібліотекою google :: protobuf. Дана бібліотека надає можливість кодування і декодування структурованих даних. На базі цієї бібліотеки я побудую простенький диспетчер, який може обробляти будь-які повідомлення. Незвичайність цього диспетчера полягає в тому, що він не буде знати типи переданих повідомлень, і буде обробляти повідомлення тільки за допомогою зареєстрованих обробників.
Короткий опис бібліотеки protobuf
Отже, спочатку коротко розглянемо бібліотеку google :: protobuf, вона поставляється у вигляді двох компонентів:
власне, сама бібліотека + заголовні файли
компілятор файлів * .proto - генерує з опису повідомлення C ++ клас (також є можливість генерації для інших мов програмування: Java, Python і т.д.)
В окремому файлі створюється опис повідомлення, з якого буде згенеровано клас, синтаксис дуже простий:
Тут ми описуємо повідомлення ServerStatusAnswer, яке має два необов'язкових поля:
- threadCount - необов'язковий цілочисельний параметр
- listeners - це необов'язковий ланцюжок, яка може кілька разів повторюватися
Насправді формат protobuf - бінарний, тут я привів повідомлення в Новомосковскемом форматі тільки для зручності сприйняття
Компілятор автоматично генерує C ++ код для сериализации і десеріалізациі подібних повідомлень. Бібліотека protobuf також надає додаткові можливості: сериализация в файл, в потік, в буфер.
Я використовую CMake як системи збирання, і в ньому вже є підтримка protobuf:
PROTOBUF_GENERATE_CPP - даний макрос викликає компілятор protoc для кожного * .proto файлу, і генерує відповідні cpp і h файли, які додаються до збірки.
Все робиться автоматично, і ніяких додаткових присідань робити не треба (Під * nix може знадобитися додатковий пакет Threads і відповідний прапор лінковщік).
опис диспетчера
Я вирішив спробувати написати диспетчер повідомлень, який приймає якесь повідомлення, викликає відповідний обробник і відправляє відповідь на отримане повідомлення. При цьому диспетчер не повинен знати типи переданих йому повідомлень. Це може бути необхідно в разі, якщо диспетчер додає або видаляє відповідні обробники в процесі роботи (наприклад, довантажити відповідний модель розширення, * .dll, * .so).
На допомогу приходить вислів: «Будь-яку проблему можна вирішити шляхом введення додаткового рівня абстракції, крім проблеми занадто великої кількості рівнів абстракції».
Нам треба відокремити визначення типу повідомлення від самого повідомлення, ми це можемо зробити наступним способом:
Ми запакуємо наше повідомлення всередину ще одного повідомлення:
- обов'язкове поле id містить унікальний ідентифікатор повідомлення
- необов'язкове поле data містить наше повідомлення
Але тепер ми отримуємо, що кожен обробник повинен проводити розпакування повідомлення sample :: proto :: Message в своє власне повідомлення. А цей процес буде дублюватися для кожного такого обробника. Ми хочемо уникнути дублювання коду, тому візьмемо патерн Type Erasure. Даний патерн дозволяє приховати тип оброблюваної суті за загальним інтерфейсом, проте кожен обробник буде працювати з конкретним типом, відомим тільки йому.
Отже, реалізація дуже проста:
Ми визначаємо віртуальну функцію process. але також додаємо віртуальну функцію doProcess. яка вже працює з нашими конкретними повідомленнями. Даний прийом заснований на механізмі інстанцірованія шаблонів: типи підставляються в момент реального використання шаблону, а не в момент декларації. А так як даний клас успадковується від MessageProcessorBase, то ми сміливо можемо передавати спадкоємців даного класу в наш диспетчер. Також необхідно зауважити, що даний клас здійснює сериализацию і десеріалізацію наших конкретних повідомлень і кидає виключення в разі виникнення помилок.
Ну і наостанок наведу приклад використання даного диспетчера, припустимо у нас є два види повідомлень:
Як видно з опису - дані повідомлення запитують у сервера його внутрішній стан (ServerStatus), і просто повертає отриманий запит (Echo). Реалізація самих оброблювачів тривіальна, я приведу реалізацію тільки ServerStatus:
Сама реалізація:
Ось як це працює:
- gcc-4.4.5-linux
- cmake-2.8.2
- boost-1.42
- protobuf-2.3.0
Приклад викладений на github