Protocol buffers
Содержание:
Code exceptions
If any prohibited Qt/QML keyword is used as field name, generator appends ‘_proto’ suffix to generated filed name. It’s required to omit overloading for example QML reserved names like ‘id’ or ‘objectName’.
E.g. for message:
Following properties will be generated:
...
Q_PROPERTY(QtProtobuf::sint32 import_proto READ import_proto WRITE setImport_proto NOTIFY import_protoChanged SCRIPTABLE true)
Q_PROPERTY(QtProtobuf::sint32 property_proto READ property_proto WRITE setProperty_proto NOTIFY property_protoChanged SCRIPTABLE true)
Q_PROPERTY(QtProtobuf::sint32 id_proto READ id_proto WRITE setId_proto NOTIFY id_protoChanged SCRIPTABLE true)
...
Примеры использования
Для того чтобы определить структуру сериализуемых данных, необходимо создать .proto-файл с исходным кодом этой структуры. Ниже приведён пример .proto-файла для 2-й версии Protocol Buffers, где описывается информация о машине: марка, тип кузова, цвет, год выпуска, и информация о предыдущих владельцах.
message Car {
required string model = 1;
enum BodyType {
sedan = ;
hatchback = 1;
SUV = 2;
}
required BodyType type = 2 default = sedan];
optional string color = 3;
required int32 year = 4;
message Owner {
required string name = 1;
required string lastName = 2;
required int64 driverLicense = 3;
}
repeated Owner previousOwner = 5;
}
После того как файл с нужной структурой данных создан, необходимо скомпилировать его компилятором для вашего языка программирования, чтобы сгенерировать класс доступа к этим данным. Этот класс будет содержать простейшие методы доступа ко всем полям типа get/set, а также методы для сериализации и десериализации вашей структуры данных в/из массива байтов.
Примечательно, что можно добавлять к уже созданной структуре данных новые поля без потери совместимости с предыдущей версией: при парсинге старых записей новые поля просто будут игнорироваться.
Known issues
Types
PHP is very weak when dealing with numbers processing. Several work arounds have been applied
to the standard binary codec to reduce incompatibilities between Protobuf types and PHP ones.
-
Protobuf stores floating point values using the IEEE 754 standard
with 64bit words for the and 32bit for the types. PHP supports IEEE 754 natively although
the precission is platform dependant, however it typically supports 64bit doubles. It means that
if your PHP was compiled with 64bit sized doubles (or greater) you shouldn’t have any problem encoding
and decoded float and double typed values with Protobuf. -
Integer values are also platform dependant in PHP.
The library has been developed and tested against PHP binaries compiled with 64bit integers. The encoding and
decoding algorithm should in theory work no matter if PHP uses 32bit or 64bit integers internally, just take
into account that with 32bit integers the numbers cannot exceed in any case the value (2147483647).While Protobuf supports unsigned integers PHP does not. In fact, numbers above the compiled PHP maximum
integer (, 0x7FFFFFFFFFFFFFFF for 64bits) will be automatically casted to doubles, which
typically will offer 53bits of decimal precission, allowing to safely work with numbers upto
0x20000000000000 (2^53), even if they are represented in PHP as floats instead of integers. Higher numbers
will loose precission or might even return an infinity value, note that the library does not include
any checking for these numbers and using them might provoke unexpected behaviour.Negative values when encoded as , or types require the big integer extensions
GMP or BC Math (the later only for 64bit architectures)
to be available in your PHP environment. The reason is that when encoding these negative numbers without
using zigzag the binary representation uses the most significant bit for the sign, thus the numbers become
above the maximum supported values in PHP. The library will check for these conditions and will automatically
try to use GMP or BC to process the value.
Strings
The binary codec expects strings to be encoded using UTF-8. PHP does not natively support string encodings,
PHP’s string data type is basically a length delimited stream of bytes, so it’s not trivial to include
automatic encoding conversion into the library encoding and decoding routines. Instead of trying to guess
or offer a configuration interface for the encoding, the binary codec will process the type just as
it would process one, delegating on your application the task of encoding or decoding in the desired
character set.
Memory usage
Large messages might be troublesome since the way the library is modelled does not allow to parse or
serialize messages as streams, instead the whole operation is performed in memory, which allows for
faster processing but could consume too much RAM if messages are too large.
Unknown fields
Since wire types are different across different codec’s formats, it’s not possible to transcode
unkwnon fields consumed in one codec to another. This means, for example, that when consuming a
message using the binary codec, if it contains unknown fields they won’t be included when
serializing the message using the Json codec.
Реализация
На данный момент компанией Google созданы компиляторы для языков программирования: C++, Java, Python, Go, C#, Objective C, JavaScript. Но существует ряд проектов сторонних разработчиков, которые создали компиляторы для следующих языков программирования: Action Script, C, C#, Clojure, Common Lisp, D, Erlang, Go, Haskell, Haxe, JavaScript, Lua, Matlab, Mercury, Objective C, Swift, OCaml, Perl, PHP, Python, Ruby, Rust, Scala, Visual Basic, Delphi.
Реализация для языка Си
Чтобы использовать протокол в языке Си без сторонних библиотек необходимо либо использовать вставки на языке C++, если таковые поддерживаются используемым компилятором, либо делать обёртки над сгенерированным для C++ кодом в виде библиотек. Если подобные варианты не подходят, то известны следующие генераторы кода:
Сериализация своими руками
Когда перед программистом встает задача упаковки структур данных, например для их последующей передачи по сети, у него есть несколько путей, по которым он может пойти. Один из них — написать все самому, с нуля. Но и тут перед ним открывается развилка из трех дорог.
Самый простой и довольно популярный способ — это представить все данные в виде строк. В этом случае на выходе мы получим поток ASCII-символов (а может быть, и не ASCII), который затем будет передан по сети или записан в файл. Если попробовать набросать схематичный код, то он будет выглядеть примерно так:
Сериализация в строки
На выходе функции save мы получим примерно такую строку: «120\n23\nsome string». Плюсы этого подхода в том, что данные остаются сравнительно читаемыми для человека, а сама реализация проста и не требует специальных знаний. А основным минусом тут будет то, что представление структур в виде строки подойдет только для очень простых наборов данных. К тому же придется писать довольно много кода для кодирования и декодирования, а скорость его выполнения будет оставлять желать лучшего.
Другой популярный метод это запись данных в XML. Разнообразных библиотек, занимающихся парсингом XML-файлов, можно насчитать великое множество, что упрощает процесс написания механизмов сериализации. Данные в этом случае представлены еще нагляднее, да и гибкость тут на высоте. Многие популярные протоколы используют этот язык разметки в качестве своей основы, так как он расширяемый и позволяет не сильно задумываться об обратной совместимости при обновлении структуры данных. К таким протоколам можно отнести SOAP или Jabber.
Но, как и в случае с предыдущим способом, чтение и запись данных в XML-формат накладывает большие ограничения на производительность. Навигация по дереву неслабо нагрузит процессор, да и код все-таки тоже придется немного дописать, чтобы все работало так, как задумано. Еще один минус, о котором многие забывают в эпоху высокоскоростного интернета, — это размер получаемых данных. При достаточно больших объемах информации или плохих сетевых соединениях использовать XML не очень целесообразно.
Ну и наконец, последний метод, который получил широкое распространение, — это упаковка в бинарный вид. В этом случае мы практически полностью теряем читабельность сериализованных данных, но зато значительно выигрываем в скорости их парсинга и выходном объеме. Все бы хорошо, но в случае байтового представления информации мы получаем кучу проблем с совместимостью при изменении структур данных в программе, а также тратим много усилий на поддержку кода упаковки и распаковки в актуальном состоянии.
Known issues
Types
PHP is very weak when dealing with numbers processing. Several work arounds have been applied
to the standard binary codec to reduce incompatibilities between Protobuf types and PHP ones.
-
Protobuf stores floating point values using the IEEE 754 standard
with 64bit words for the and 32bit for the types. PHP supports IEEE 754 natively although
the precission is platform dependant, however it typically supports 64bit doubles. It means that
if your PHP was compiled with 64bit sized doubles (or greater) you shouldn’t have any problem encoding
and decoded float and double typed values with Protobuf. -
Integer values are also platform dependant in PHP.
The library has been developed and tested against PHP binaries compiled with 64bit integers. The encoding and
decoding algorithm should in theory work no matter if PHP uses 32bit or 64bit integers internally, just take
into account that with 32bit integers the numbers cannot exceed in any case the value (2147483647).While Protobuf supports unsigned integers PHP does not. In fact, numbers above the compiled PHP maximum
integer (, 0x7FFFFFFFFFFFFFFF for 64bits) will be automatically casted to doubles, which
typically will offer 53bits of decimal precission, allowing to safely work with numbers upto
0x20000000000000 (2^53), even if they are represented in PHP as floats instead of integers. Higher numbers
will loose precission or might even return an infinity value, note that the library does not include
any checking for these numbers and using them might provoke unexpected behaviour.Negative values when encoded as , or types require the big integer extensions
GMP or BC Math (the later only for 64bit architectures)
to be available in your PHP environment. The reason is that when encoding these negative numbers without
using zigzag the binary representation uses the most significant bit for the sign, thus the numbers become
above the maximum supported values in PHP. The library will check for these conditions and will automatically
try to use GMP or BC to process the value.
Strings
The binary codec expects strings to be encoded using UTF-8. PHP does not natively support string encodings,
PHP’s string data type is basically a length delimited stream of bytes, so it’s not trivial to include
automatic encoding conversion into the library encoding and decoding routines. Instead of trying to guess
or offer a configuration interface for the encoding, the binary codec will process the type just as
it would process one, delegating on your application the task of encoding or decoding in the desired
character set.
Memory usage
Large messages might be troublesome since the way the library is modelled does not allow to parse or
serialize messages as a streams, instead the whole operation is performed in memory, which allows for faster
processing but could consume too much RAM if messages are too large.
Unknown fields
Since wire types are different across different codec’s formats, it’s not possible to transcode unkwnon
fields consumed in one codec to another. This means, for example, that when consuming a message using the
binary codec, if it contains unknown fields, they won’t be included when serializing the message using the
Json codec.
Формат протокола
В общем виде формат представляет собой закодированную последовательность полей, состоящих из ключа и значения. В качестве ключа выступает номер, определённый для каждого поля сообщения в proto-файле. Перед каждым полем указываются совместно закодированные номер поля в формате varint и тип поля. Если в качестве типа указана строка (), вложенное сообщение, повторяющееся сообщение или набор байт (), то следом идёт размер данных в формате varint. Далее идёт значение, соответствующее полю (данные).
Число в формате varint ( и ) кодируется в последовательность байт, в которой у всех байт, кроме последнего, старший бит (MSB) выставляется в 1. При преобразовании в стандартное представление старший бит каждого байта отбрасывается, а оставшиеся 7-битные составляющие соединяются друг с другом в обратном порядке. Формат восьмиразрядного varint был выбран для уменьшения размера пакета при передаче небольших чисел. Так, если число меньше 128, то оно будет занимать лишь 1 байт. Однако, числа близкие к максимально возможным, будут занимать больше места, чем в обычном формате. Например, максимальное значение, которое можно сохранить в 8-ми байтах, в формате varint — 10 байт. Отрицательные числа в формате varint всегда занимают наибольший размер, в зависимости от типа, поскольку старший бит у знакового числа выставлен в 1.
Проблема кодирования отрицательных чисел была решена использованием алгоритма ZigZag ( и ), суть которого сводится к переносу бита знака из старшего разряда в младший. Кодирование алгоритмом ZigZag предполагает, что положительные и отрицательные числа будут чередоваться друг с другом с увеличением закодированного значения. В таком случае чётные числа будут положительными, а нечётные — отрицательными.
Пусть — исходное значение, а — разрядность типа данных исходного значения, а — закодированное алгоритмом ZigZag значение, тогда кодирование можно записать с помощью выражения на языке Си:
encoded_value = (value << 1) ^ (value >> (N - 1));
Следует учесть, что вторая операция сдвига является арифметическим сдвигом, то есть при сдвиге вправо отрицательного числа старшие биты заполняются единицами, а не нулями (сдвиг знакового бита).
Декодирование выполняется более сложным способом: выполняется исключающее ИЛИ над закодированным значением, сдвинутым на 1 вправо для удаления знакового бита, и знаковым битом, полученным из закодированного числа, спроецированным на все биты через умножение на максимальное значение для разрядов. Таким образом, знаковый бит переносится из младшего разряда в старший:
uvalue = ((encoded_value & 1) * MAX_VALUE(N)) ^ (encoded_value >> 1);
Значение соответствует значению с разрядами, заполненными единицами (например, при =32). Таким образом, умножение младшего бита, установленного в 1, на это число будет соответствовать значению -1 в знаковом типе данных. В языке Си декодирование необходимо осуществлять для значений и беззнакового типа, а затем значение должно преобразовываться в знаковый тип, не меняя битовое представление.
Все числовые значения, кроме , и , в протоколе кодируются в формате varint.
Overview
developed Protocol Buffers for internal use and provided a code generator for multiple languages under an open source license (see ).
The design goals for Protocol Buffers emphasized simplicity and performance. In particular, it was designed to be smaller and faster than XML.
Protocol Buffers are widely used at Google for storing and interchanging all kinds of structured information. The method serves as a basis for a custom remote procedure call (RPC) system that is used for nearly all inter-machine communication at Google.
Protocol Buffers are similar to the Apache Thrift (used by Facebook) or Microsoft Bond protocols, offering as well a concrete RPC protocol stack to use for defined services called gRPC.
Data structures (called messages) and services are described in a proto definition file (.proto) and compiled with protoc. This compilation generates code that can be invoked by a sender or recipient of these data structures. For example, example.pb.cc and example.pb.h are generated from example.proto. They define C++ classes for each message and service in example.proto.
Canonically, messages are serialized into a binary wire format which is compact, forward- and backward-compatible, but not self-describing (that is, there is no way to tell the names, meaning, or full datatypes of fields without an external specification). There is no defined way to include or refer to such an external specification (schema) within a Protocol Buffers file. The officially supported implementation includes an ASCII serialization format, but this format—though self-describing—loses the forward- and backward-compatibility behavior, and is thus not a good choice for applications other than debugging.
Though the primary purpose of Protocol Buffers is to facilitate network communication, its simplicity and speed make Protocol Buffers an alternative to data-centric C++ classes and structs, especially where interoperability with other languages or systems might be needed in the future.
Direct usage of generator
:QML:COMMENTS:FOLDER"] protoc --plugin=protoc-gen-qtprotobuf=<path/to/bin/>qtprotobufgen --qtprotobuf_out=<output_dir> [-I/extra/proto/include/path] <protofile>.proto
QT_PROTOBUF_OPTIONS
For protoc command you also may specify extra options using QT_PROTOBUF_OPTIONS environment variable and colon-separated format:
:QML:COMMENTS:FOLDER"] protoc --plugin=protoc-gen-qtprotobuf=<path/to/bin/>qtprotobufgen --qtprotobuf_out=<output_dir> [-I/extra/proto/include/path] <protofile>.proto
Following options are supported:
SINGLE — enables single-file generation when for each .proto file single pair of .h .cpp files is generated
MULTI — enables multi-file generation when for each message separate pair of .h .cpp
QML — enables QML code generation in protobuf classes. If is set QML-related code for lists and QML registration to be generated.
COMMENTS — enables comments copying from .proto files
FOLDER — enables folder-based generation
Шаг 4. Взаимодействуем со сгенерированным кодом
Компилятор создает весь код внутри файла , это название мы указали в .proto файле. Весь сгенерированный
код доступен в одноименном классе. Messages превратились в несколько внутренних классов, которые помогают создавать,
сериализовывать и десериализовывать описанную модель. По message Student компилятор создал класс AcademyProtos.Student и
AcademyProtos.Student.Builder. Это типичная реализация паттерна “Строитель”. Объекты класса Student всегда неизменяемы,
т.е. после создания мы не можем изменить каких-либо значений. Все манипуляции происходят с классом Builder, для этого у него есть
достаточно методов.
Разберем код. Нам небходимо создать группу, для которой определено обязательное имя и набор студентов в виде repeated
поля. Создание группы выглядит следующим образом:
Для того, чтобы создать новый объект мы должны вызвать его Builder, заполнить его поля, а затем, в качестве звершающей
изменения операции вызвать метод , который создаст группу. Repeated поля мы можем заполнять как по одному, так и
добавлять целую коллецию.
Как вы уже поняли, создавать студентов мы можем аналогично:
Итак, данные мы создали, получили заполненный объект типа Group, теперь необходимо перегнать его в массив байт.
Сделать это можно следующим образом:
Вот так просто! Сериализованная группа теперь — набор байт в protocol buffers формате.
Затем нам необходимо прочитать сохраненные данные. Воспользуемся статическим методом .
Для того, чтобы проверить результат выведем его на экран (компилятор создает человекопонятные методы toString для классов, так
что с отладкой нет проблем).
В результате, в консоли мы видим:
За ширмой, для полноты примера, я добавил еще одного студента к группе.
Заключение
Protocol Buffers — отличный инструмент для кросс-платформенной сериализации данных. В некоторых случаях, он позволяет
сохранять обратную совместимость, однако, при безрассудном подходе может и нанести вред. Сегодня мы познакомились с
основами формата, разобрали .proto файл и пример Java кода, который работает с описанными структурами. Protocol Buffers —
это кирпичик, который стоит в основе других технологий для интеграции гетерогенных систем, также существуют и аналоги,
которые мы рассмотрим позже. Как всегда — это не серебряная пуля, но хороший инструмент интеграции.
Код проектаОфициальный Java Tutorial
Please enable JavaScript to view the comments powered by Disqus.
comments powered by Disqus
*Шаг 2. Компилируем файл
* опциональный, для понимания
Созданный .proto файл нужно скомпилировать и прежде всего нам нужен компилятор. Скачиваем
архив. В архиве к нам прилетает компилятор и некоторый набор типов, которые мы можем использовать из коробки.
Когда вы нашли место для файла в вашей файловой системе добавьте его в PATH. В Windows это делается в Параметрах окружения,
а в linux будет достаточно выполнить . Теперь нам доступен компилятор из терминала, давайте скомпилируем.
Перейдем в папку с .proto файлом и выполним команду:
Флаг указывает на папку куда будет сгенерирован java код. В этой папке мы получили иерархию, которая
определяет java package, который мы указали в .proto файле. Результат компиляции — .java файл, который пока не
компилируется javac’ом, для этого нам необходима дополнительная библиотека для работы с protobuf из java. В целях
избежения ненужных проблем, перенесем наши эксперименты в плоскость обычного проекта.
What are Protocol buffers and how do they work?
Generally speaking, Protocol Buffers are a defined interface for the serialization of structured data. It defines a normalized way to communicate, utterly independent of languages and platforms.
Google advertises its ProtoBuf like this:
The ProtoBuf interface describes the structure of the data to be sent. Payload structures are defined as “messages” in what is called Proto-Files. Those files always end with a extension.For example, the basic structure of a todolist.proto file looks like this. We will also look at a complete example in the next section.
Those files are then used to generate integration classes or stubs for the language of your choice using code generators within the protoc compiler. The current version, Proto3, already supports all the major programming languages. The community supports many more in third-party open-source implementations.
Generated classes are the core elements of Protocol Buffers. They allow the creation of elements by instantiating new messages, based on the files, which are then used for serialization. We’ll look at how this is done with Python in detail in the next section.
Independent of the language for serialization, the messages are serialized into a non-self-describing, binary format that is pretty useless without the initial structure definition.
The binary data can then be stored, sent over the network, and used any other way human-readable data like JSON or XML is. After transmission or storage, the byte-stream can be deserialized and restored using any language-specific, compiled protobuf class we generate from the .proto file.Using Python as an example, the process could look something like this:

First, we create a new todo list and fill it with some tasks. This todo list is then serialized and sent over the network, saved in a file, or persistently stored in a database.
The sent byte stream is deserialized using the parse method of our language-specific, compiled class.Most current architectures and infrastructures, especially microservices, are based on REST, WebSockets, or GraphQL communication. However, when speed and efficiency are essential, low-level RPCs can make a huge difference.
Instead of high overhead protocols, we can use a fast and compact way to move data between the different entities into our service without wasting many resources.
But why isn’t it used everywhere yet?
Protocol Buffers are a bit more complicated than other, human-readable formats. This makes them comparably harder to debug and integrate into your applications.
Iteration times in engineering also tend to increase since updates in the data require updating the proto files before usage.
Careful considerations have to be made since ProtoBuf might be an over-engineered solution in many cases.
What alternatives do I have?
Several projects take a similar approach to Google’s Protocol Buffers.
Google’s Flatbuffers and a third party implementation, called Cap’n Proto, are more focused on removing the parsing and unpacking step, which is necessary to access the actual data when using ProtoBufs. They have been designed explicitly for performance-critical applications, making them even faster and more memory efficient than ProtoBuf.When focusing on the RPC capabilities of ProtoBuf (used with gRPC), there are projects from other large companies like Facebook (Apache Thrift) or Microsoft (Bond protocols) that can offer alternatives.
Общие сведения
По замыслу разработчиков, сначала должна быть описана структура данных, которая затем компилируется в классы. Вместе с классами идёт код их сериализации в компактном формате представления. Чтение и запись данных доступна в высокоуровневых языках программирования — таких как Java, C++ или Python.
В 2010 году бэкенд перешёл на Protocol Buffers. По заявлению разработчиков , база в триллион твитов на XML занимала бы десять петабайт вместо одного.
По заявлениям , Protocol Buffers по сравнению с XML:
- проще;
- от 3 до 10 раз меньше;
- от 20 до 100 раз быстрее;
- более однозначный;
- позволяет создавать классы, которые в дальнейшем легче использовать программно.
Protocol Buffers не предназначен для чтения пользователем и представляет собой двоичный формат. Для десериализации данных необходим отдельный .proto-файл, в котором определяется формат сообщения.
Integration with CMake project
You can integrate QtProtobuf as submodule in your project or as installed in system package. Add following line in your project CMakeLists.txt:
...
find_package(QtProtobufProject CONFIG REQUIRED COMPONENTS QtProtobuf QtGrpc)
file(GLOB PROTO_FILES ABSOLUTE ${CMAKE_CURRENT_SOURCE_DIR}/path/to/protofile1.proto
${CMAKE_CURRENT_SOURCE_DIR}/path/to/protofile2.proto
...
${CMAKE_CURRENT_SOURCE_DIR}/path/to/protofileN.proto)
# Function below generates source files for specified PROTO_FILES,
# and link them to the MyTarget as static library
add_executable(MyTarget main.cpp) # Add your target here
qtprotobuf_generate(TARGET MyTarget
OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated
PROTO_FILES ${PROTO_FILES})
Another option is to pre-specify expected generated headers to prevent dummy-parser mistakes
...
set(GENERATED_HEADERS
# List of artifacts expected after qtprotobufgen job done.
# Usually it's full list of messages in all packages with .h header suffix
...
)
...
qtprotobuf_generate(TARGET MyTarget
OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/generated
PROTO_FILES ${PROTO_FILES}
GENERATED_HEADERS ${GENERATED_HEADERS})
...
In case if you somehow avoided usage, you need manualy link QtProtobuf libraries used by project, e.g.:
...
target_link_libraries(${TARGET} QtProtobuf::QtProtobuf QtProtobuf::QtGrpc QtProtobuf::QtProtobufWellKnownTypes)
...
qtprotobuf_generate
qtprotobuf_generate is cmake helper function that automatically generates STATIC library target from your .proto files
Parameters:
TARGET — name of you target that generated code archive will be linked to
GENERATED_TARGET — name that will be used for generated archive library target. It’s usefull when you supposed to have multiple generated targets to be linked to single one.
OUT_DIR — output directory that will contain generated artifacts. Usually subfolder in build directory should be used
GENERATED_HEADERS — List of header files expected after generator job finished
EXCLUDE_HEADERS — List of header files to be excluded from pre-parsed list of expected header files (e.g. nested messages that are not supported by QtProtobuf generator)
PROTO_FILES — List of .proto files that will be used in generation procedure
Options:
MULTI — Enables multi-files generation mode. If provided in parameter list generator will create pair of header/source files for each message
QML — Enables QML code generation in protobuf classes. If provided in parameter list QML related code for lists and QML registration to be generated.
COMMENTS — Enables comments copying from .proto files. If provided in parameter list message and field related comments will be copied to generated header files.
FOLDER — Enables folder based generation. If provided in parameter list generator will place generated artifacts to folder structure according to package of corresponding .proto file
qtprotobuf_link_target
qtprotobuf_link_target is cmake helper function that links generated protobuf target to your binary. It’s useful when you try to link generated target to shared library or/and to executable that doesn’t utilize all protobuf generated classes directly from C++ code, but requires them from QML.
Parameters:
TARGET — name of target to link to
GENERATED_TARGET — protobuf generated target name
Usefull definitions
QT_PROTOBUF_MAKE_COVERAGE — if TRUE/ON for QtProtobuf project build, QtProtobuf will be built with gcov intergration, to collect code coverage reports(usefull for developers). FALSE by default
QT_PROTOBUF_MAKE_TESTS — if TRUE/ON for QtProtobuf project build, tests for QtProtobuf will be built. TRUE by default
QT_PROTOBUF_MAKE_EXAMPLES — if TRUE/ON for QtProtobuf project build, built-in examples will be built. TRUE by default
QT_PROTOBUF_EXECUTABLE — contains full path to QtProtobuf generator add_executable
Шаг 1. Определяем формат протокола
На первом шаге нам нужно описать .proto файл. Разберем сокращенную версию предметной области учебного заведения.
Исходный .proto файл выглядит следующим образом:
Разберемся с синтаксисом файла. Прежде всего мы указываем какую версию protobuf мы используем, в нашем случае это вторая
версия. Затем указываем package, который необходим здесь для разделения пространств имён. Т.к. мы знаем, что будем пользоваться
java, то указываем две дополнительные настройки: и . Первая, очевидно, говорит в
какой пакет и соответственно иерархию директорий необходимо сложить результат компиляции, а
определяет имя файла, который будет в себя заворачивать весь сгенерированный контент. Если это не будет сделано, то
компилятор определит имя в соответствии с CamelCase по названию .proto файла. Эти настройки, как вы понимаете, java-специфичны.
Модификаторы дают нам больше представления о том как поле используется, например, модификатор required позволяет описать
обязательное поле в сообщении, если десериализатор не обнаружит этого поля, то весь процесс десериализации закончится с ошибкой.
Это важно учитывать при проектировании API (снова взгляните на второй абзац в разделе “Недостатки” этой статьи).
Модификатор optional, говорит о том, что поле может быть, а может отсутствовать, своего рода nullable поле. Модификатор repeated
используется для работы с множеством значений для одного поля (аналогично коллекциям в Java)
Вы можете вкладывать messages друг в друга, использовать перечисления enum, в общем очень похоже на Java. Кроме того,
есть возможность определить значения по умолчанию.
Альтернативные протоколы
Наиболее высокопроизводительной альтернативой может служить библиотека FlatBuffers (англ.)русск., которая позволяет обращаться к сериализованным данным без их копирования по частям в отдельные области памяти. Соответственно, данные передаются в том же виде, в каком и используются, в связи с чем увеличивается объём передаваемых данных.
Проект Apache Avro (англ.)русск. отличается тем, что не требует генерации кода при изменении схемы данных при использовании в динамически-типизируемых языках, а сама схема описывается в формате JSON.
Сравнение с Apache Thrift
Ключевыми особенностями Apache Thrift могут служить возможность передачи ассоциативных массивов, списков и множеств, а также встроенная поддержка удалённого вызова процедур.
| Protocol Buffers | Apache Thrift | |
|---|---|---|
| Разработчик | Facebook, Apache | |
| Поддерживаемые языки | C++, Dart, Go, Java, Python, Ruby, C#, Objective C, JavaScript и PHP | C++, Java, JavaScript, Python, PHP, XSD, Ruby, C#, Perl, Objective C, Erlang, Smalltalk, OCaml, and Haskell |
| Исходящие форматы | Бинарный | Бинарный, JSON |
| Простые типы | bool
32/64-bit integers |
bool
byte |
| Константы | Нет | Да |
| Составной тип | Сообщение | Структура |
| Исключения | Нет | Да |
| Документация | Хорошая | Скудная |
| Лицензия | BSD-style | Apache |
| Расширения составных типов | Да | Нет |
Features
Working
- Standard types (numbers, string, enums, messages, etc)
- Extensions, Unknown and Packed fields
- Generate service interfaces
- Protoc compiler plugin to generate the PHP classes
- Template based code generation. Go crazy and customize the generated code 🙂
- Include comments from .proto files in the generated files
- Pluggable serialization backends (codecs)
- Standard Binary
- Standard TextFormat ¹
- PhpArray
- JSON
- ProtoJson (TagMap and Indexed variants)
- XML
- Reflection capabilities
- Dynamic messages with annotations support (no code generation step required)
- Lazy decoding of messages to improve the performance in real world scenarios
¹ Only serialization is supported in this codec
serde_derive support
(Only in master, not released yet)
Rust-protobuf can be used with serde.
To enable you need to:
- Enable serde option
with when codegen is invoked programmatically:
with stable rust-protobuf:
protoc_rust::run(protoc_rust::Args {
...
customize: Customize {
serde_derive: Some(true),
..Default::default()
},
});
with rust-protobuf from master:
protoc_rust::Args::new()
...
.customize(Customize {
serde_derive: Some(true),
..Default::default()
})
.run()?;
or in file:
import "rustproto.proto"; option (rustproto.serde_derive_all) = true;
You may now and messages:
let my_message = MyMessage::new(); serde_json::to_string(&my_message).unwrap();
Integration with qmake project
QtProtobuf has limited qmake build procedures support. It’s only available and tested on linux platforms. To use it in your qmake project, first you need build and install QtProtobuf as standalone project in your system paths:
mkdir build cd build cmake .. [-DCMAKE_PREFIX_PATH="<path/to/qt/installation>/Qt<qt_version>/<qt_version>/gcc_64/lib/cmake"] -DCMAKE_INSTALL_PREFIX=/usr -DQT_PROTOBUF_MAKE_TESTS=OFF -DQT_PROTOBUF_MAKE_EXAMPLES=OFF cmake --build . -- -j<N> sudo cmake --build . --target install
Commands above will install qt protobuf into you system paths prefixed with /usr folder.
Once build and installation is finished, you may use QtProtobuf and QtGrpc in qmake project by adding following lines in .pro file:
QT += protobuf #for protobuf libraries support QT += grpc #for grpc libraries support
To generate source code and link it to you project use predefined qtprotobuf_generate function
qtprotobuf_generate()
Parameters:
generate_qml — Enables/disables QML code generation in protobuf classes. If set to QML-related code for lists and QML registration to be generated.