вторник, 27 ноября 2012 г.

Основы protobuf

Если вы еще используете JSON, то Google protobuf идет к вам!

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

Поставленные цели перекликаются с тем, что знаем по технологиям JSON и оригинального XML, однако технология protobuf имеет ряд преимуществ в ключе своей завершенности. Т.е. если средства, обычно предоставляемые для работы с JSON или XML являются некоторыми низкоуровневыми инструментами, то protobuf обеспечивается инструментами самого высокого уровня.

Надо отметить, что, наверное единственный недостаток технологии заключается в необходимости тянуть в проект дополнительные связи с внешней библиотекой. Так, например, работая с JSON или XML, я часто использую свои парсеры и синтезаторы пакетов, чтобы не использовать сторонних библиотек, усложняющих сборку проекта на стороне. Здесь же вся соль технологии заключается в обработке метаописаний и, поэтому, без установки и использования специальных инструментов просто не обойтись.

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

На момент написания этой статьи, Google предоставлял технологию protobuf для языков C++, Java и Python. Кроме того, сторонними заинтересованными компаниями, группами и лицами предоставлена реализация этой технологии для пары десятков других известных языков и, даже, специально для сред разработки.

Чтобы начать использование технологии protobuf с помощью средств предоставляемых Google следует скачать необходимый установочный пакет (под вашу операционную систему) со страницы code.google.com/p/protobuf/downloads/list. Для своего Linux Ubuntu 11.10 я взял вот такой пакет с исходными кодами продукта - protobuf-2.4.1.tar.bz2. Это пакет собран в классическом GNU toolchain и для его сборки и установки надо в директории разархивированного пакета выполнить следующий набор команд в консоли.

$ ./configure
$ make
$ make check
$ sudo make install

Подробности сборки читайте в файле README.txt. Тем кто не знаком с такой системой сборки можно дать только один совет - как можно скорее познакомьтесь с ней. Обратите внимание на цель make check. По этой цели вызывается система автоматических тестов собранного пакета.

После установки пакета можно начинать эксперименты по его использованию. Здесь нам, прежде всего, помогут следующие ссылки на оригинальные ресурсы Goggle.

Получив начальное представление о технологии можно познакомиться с конкретной реализацией на простом примере.

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

package tutorial;

message Person {
    required string name = 1;
    required uint32 id = 2;
    optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

message AddressBook {
    repeated Person person = 1;
}

В общем, метаописание достаточно понятное. Подробности следует смотреть в руководстве к метаязыку описания данных. Сейчас дадим лишь несколько пояснений.

Объявление пакета (package) вводит в контексте C++ одноименное пространство имен, что предотвращает возможные коллизии данных.

Представленное метаописание содержит три объекта данных: адресная книга (AddressBook), единица записи в адресной книге (Person) и составная часть описания Person - объект телефонного номера (PhoneNumber). По каждому из этих объектов будет формироваться сообщение в пакете сериализации, поэтому для метаописания этих объектов используется ключевое слово message.

Каждый элемент данных сопровождается модификатором: required, optional и repeated.

  1. required - элемент обязательно присутствует в сообщении.
  2. optional - элемент опционален. Может не присутствовать в сообщении.
  3. repeated - элемент может повторяться в сообщении любое количество раз, включая ноль раз.

Таким образом, в нашем описании описана адресная книга (AddressBook), которая может состоять из любого количества элементов Person. Каждый элемент Person, состоит из четрех полей: name, id, email и phone. Поле email - опционально, а поле phone может включать любое число объектов сообщения типа PhoneNamber. Элемент PhoneNumber состоит из двух полей - номера телефона (number) и его типа (type), значение которого определяется специальным типом нумератором PhoneType. Тип номера - опционален.

В метаописании данных допускается использование нескольких примитивных типов, в том числе: bool, int32, uint32, float, double и string. Полный список примитивных типов можно найти в этом разделе руководства метаописания данных. В дополнение к этому, можно составлять типы нумераторы и организовывать иерархию сообщений используя их как типы.

Отдельно следует сказать о тегах, которые определяют уникальность элемента данных внутри записи. Номера тегов записываются в конце каждого элемента сообщения через знак "=". Номера этих тегов делятся на две группы. Группа 1-15 и группа >=16. Различие этих групп в особенностях двоичного кодирования данных, которое мне пока не совсем понятно.

Итак, у нас есть метаописание адресной книги. В нашем случае оно лежит в файле addressbook.proto. Создадим по нему систему классов в пространстве имен tutorial к языку C++. Для этого отдадим файл метаописания утилите protoc следующим образом.

$ protoc --cpp_out=. addressbook.proto

При успешном выполнении этой операции мы получим два файла: addressbook.pb.h и addressbook.pb.cc. Это заголовочный файл и файл реализации для классов составленных по метаописанию. Теперь надо включить эти классы в пространство нашего проекта и можно начинать их использовать в коде проекта.

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

#include "addressbook.pb.h"

int main(int /*argc*/, char */*argv*/[])
{
    // Создаем экземпляр класса адресной книги для сериализации
    tutorial::AddressBook src_book;
    {
        // Создаем и заполняем первую запись в адресной книге
        tutorial::Person * person = src_book.add_person();
        person->set_name("Alexey Knyazev");
        person->set_id(0);
        person->set_email("knzsoft@mail.ru");
        {
            tutorial::Person_PhoneNumber * pn = person->add_phone();
            pn->set_number("+7 927-220-35-67");
            pn->set_type(tutorial::Person_PhoneType_MOBILE);
        }
        {
            tutorial::Person_PhoneNumber * pn = person->add_phone();
            pn->set_number("+7 962-622-31-67");
            pn->set_type(tutorial::Person_PhoneType_MOBILE);
        }
    }
    {
        // Создаем и заполняем вторую запись в адресной книге
        tutorial::Person * person = src_book.add_person();
        person->set_name("Danilov Dmitry");
        person->set_id(1);
        {
            tutorial::Person_PhoneNumber * pn = person->add_phone();
            pn->set_number("8 (8452) 43-96-86");
            pn->set_type(tutorial::Person_PhoneType_HOME);

        }
    }

    std::string msg;
    src_book.SerializeToString(&msg);

    tutorial::AddressBook dst_book;
    dst_book.ParseFromString(msg);

    dst_book.PrintDebugString();

    return 0;
}

Большую часть представленного примера программы составляет заполнение адресной книги. Из кода видно, что API для технологии protobuf достаточно простой и интуитивно понятный.

После заполнения книги мы сериализуем данные созданной адресной книги в строку msg. После чего создаем объект другой адресной книги и заполняем его через десериализацию сообщения msg. В завершении, пользуясь отладочными API, выводим десериализованную адресную книгу в стандартное устройство вывода.

Вот что выводится в консоль в результате выполнения этой программы.

person {
  name: "Alexey Knyazev"
  id: 0
  email: "knzsoft@mail.ru"
  phone {
    number: "+7 927-220-35-67"
    type: MOBILE
  }
  phone {
    number: "+7 962-622-31-67"
    type: MOBILE
  }
}
person {
  name: "Danilov Dmitry"
  id: 1
  phone {
    number: "8 (8452) 43-96-86"
    type: HOME
  }
}

Напомню, что во всем этом есть небольшая ложка дегтя - необходимость линковки с библиотекой обеспечивающей фундамент технологии protobuf, с библиотекой libprotobuf. Я делал пример в QtCreator с использованием системы сборки QMake и мой проектный файл выглядит следующим образом.

TARGET = app-1
CONFIG += console
CONFIG -= app_bundle

LIBS += -lprotobuf

TEMPLATE = app

SOURCES += main.cpp \
           addressbook.pb.cc

HEADERS += addressbook.pb.h

OTHER_FILES += \
    addressbook.proto

В заключении следует заметить, что если надо внести изменения в структуру данных подлежащих сериализации, то понадобится перезапустить метакомпилятор protoc по новому описанию и убедиться, что в пространстве проекта лежат измененые файлы - результаты работы метакомпилятора. Соответственно понадобится изменить и проект, но только по части кода заполнения и извлечения данных из предоставленных бизнес-объектов.

Таким образом, основным достоинством технологии является простота использования. Сделал метаописание бизнес-объекта и получил готовый код бизнес-объект и его сериализации. Все что остается - использовать полученный код.

Наверное следует добавить, что предоставленные средства дают возможность не только выполнять сериализацию в строку, но и в поток, что позволяет сразу вывести данные в файл, сеть или куда-то еще, что поддерживает обычные C++ потоки типа std::ostream и std::istream.

Комментариев нет: