Руководство по языку Protobuf (proto3)

Java JSON переводчик protobuf

предисловие

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

определить тип сообщения

Начнем с очень простого примера. Предположим, вы хотите определить формат сообщения «поискового запроса».Каждый запрос содержит строку запроса, количество страниц, на которых расположены интересующие вас результаты запроса, и количество результатов запроса на страницу. Файл .proto типа сообщения можно определить следующим образом:

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • Первая строка файла указывает, что вы используетеproto3Синтаксис: если вы этого не сделаете, компилятор protobuf будет считать, что вы используетеproto2. Это должна быть первая непустая строка файла без комментариев.
  • сказалSearchRequestВ определении сообщения указаны три поля (пары имя/значение), по одному для каждой части данных, которые должны быть включены в сообщение этого типа. Каждое поле имеет имя и тип.

Укажите тип поля

В приведенном выше примере все поляСкалярный тип: два целых числа (page_numberиresult_per_page) и строка (query). Однако вы также можете указать составные типы для полей, в том числеперечислитьи другие типы сообщений.

Присвоить идентификационный номер

Как и в приведенном выше формате файла, в определении сообщения каждое поле имеет уникальныйчисловой идентификатор. Эти идентификаторы используются в сообщениидвоичный форматПоля, которые идентифицируют каждое поле, не могут быть изменены после его использования. Примечание. Идентификационный номер в пределах [1,15] будет занимать один байт во время кодирования. Идентификационный номер в пределах [16,2047] занимает 2 байта. Следовательно, идентификационные номера в пределах [1,15] должны быть зарезервированы для тех элементов сообщения, которые появляются часто. ВАЖНО: зарезервируйте некоторые идентификаторы для часто встречающихся идентификаторов, которые могут быть добавлены в будущем.

Наименьший идентификационный номер может начинаться с 1 и доходить до 2 ^ 29 - 1, или 536 870 911. Идентификационные номера [19000-19999] использовать нельзя, они зарезервированы при реализации протокола Protobuf. Если вы должны использовать эти зарезервированные идентификационные номера в файле .proto, во время компиляции будет поднято предупреждение.

Укажите правила поля

Поле сообщения может быть одним из следующих:

  • Единственное число: правильно сформированное сообщение может содержать ноль или одно (но не более одного) это поле.
  • repeated: Это поле может повторяться любое количество раз (включая нули) в правильно сформированном сообщении. Порядок повторяющихся значений будет сохранен.

В прото3,repeatedПоля числового типа используются по умолчаниюpackedкодирование.

packedты сможешькодирование буфера протоколаУзнайте больше о кодировании.

Добавить больше типов сообщений

доступны в одном.protoВ файле определены различные типы сообщений. Это полезно, если вы хотите определить несколько связанных сообщений, например, если вы хотите определитьSearchResponseФормат ответного сообщения, соответствующий типу сообщения, которое может быть добавлено к тому же сообщению.proto:

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

message SearchResponse {
 ...
}

добавить заметки

быть.protoДля добавления комментариев к файлам используйте стиль C/C++.//и/* ... */грамматика.

/ * SearchRequest表示搜索查询,带有分页选项
 *表明响应中包含哪些结果。* /

message SearchRequest {
  string query = 1;
  int32 page_number = 2; //我们想要哪个页码?
  int32 result_per_page = 3; //每页返回的结果数。
}

зарезервированный текст

Если полностью удалить поле или закомментировать еговозобновитьтип сообщения, будущие пользователи могут повторно использовать номер поля при внесении собственных обновлений типа. Это может вызвать серьезные проблемы, если та же самая старая версия будет загружена позже..proto, включая повреждение данных, ошибки конфиденциальности и т. д. Один из способов убедиться, что этого не происходит, — указать номер поля (и/или имя, что также может вызвать проблемы с сериализацией JSON) удаленного поля.reserved. Компилятор буфера протокола будет жаловаться, если какие-либо будущие пользователи попытаются использовать эти идентификаторы полей.

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

Обратите внимание, что вы не можетеreservedСмешайте имена полей и номера полей в операторе.

какая у тебя сборка.proto?

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

  • заC ++, компилятор сгенерирует.hи.ccдокумент.protoи укажите класс для каждого типа сообщений, описанного в вашем файле.
  • заJava, компилятор генерирует.javaфайл, содержащий классы для каждого типа сообщений, иBuilderСпециальный класс для создания экземпляров классов сообщений.
  • PythonНемного иначе — компилятор Python генерирует модуль, содержащий статические дескрипторы для каждого типа сообщения,.protoзатем сметаклассИспользуются вместе для создания необходимых классов доступа к данным Python во время выполнения.
  • заGo, компилятор будет.pb.goДля каждого типа сообщения в файле создается один тип файла.
  • заRuby, компилятор генерирует.rbФайл, содержащий модули Ruby для типов сообщений.
  • заObjective-C, компилятор генерируетpbobjc.hиpbobjc.mдокумент.proto, который содержит класс для каждого типа сообщения, описанного в файле.
  • заС#, компилятор будет.csСоздать один файл из каждого файла.proto, который содержит класс для каждого типа сообщения, описанного в файле.

Вы можете следовать руководству для выбранного вами языка (скоро появится версия proto3), чтобы узнать больше об использовании API для каждого языка. Дополнительные сведения об API см. в соответствующихСправочник по API(версия proto3 скоро).

Тип скалярного значения

Скалярные поля сообщения могут иметь один из следующих типов – в таблице показано.protoТипы, указанные в файле, и соответствующие типы в автоматически сгенерированных классах:

.proto type notes C ++ type Java type Python type [2] Type Ruby type C# type PHP type
double double double float float64 float double float
float float float float FLOAT32 float float float
INT32 Используйте кодировку переменной длины. Неэффективное кодирование отрицательных чисел — если ваше поле может иметь отрицательные значения, используйте вместо этого sint32. INT32 INT INT INT32 Fixnum or Bignum (as needed) INT Integer
Int64 Используйте кодировку переменной длины. Неэффективное кодирование отрицательных чисел — если ваше поле может иметь отрицательные значения, используйте вместо этого sint64. Int64 long int / long [3] Int64 TWINS long Integer/string[5]
UINT32 Используйте кодировку переменной длины. UINT32 int [1] int / long [3] UINT32 Fixnum or Bignum (as needed) UINT Integer
UINT64 Используйте кодировку переменной длины. UINT64 Long [1] int / long [3] UINT64 TWINS ULONG Integer/string[5]
SINT32 Используйте кодировку переменной длины. Целочисленное значение со знаком. Они кодируют отрицательные числа более эффективно, чем обычные int32. INT32 INT INT INT32 Fixnum or Bignum (as needed) INT Integer
sint64 Используйте кодировку переменной длины. Целочисленное значение со знаком. Они кодируют отрицательные числа более эффективно, чем обычные int64. Int64 long int / long [3] Int64 TWINS long Integer/string[5]
fixed32 Всегда четыре байта. Более эффективен, чем uint32, если значение обычно больше 2 28. UINT32 int [1] int / long [3] UINT32 Fixnum or Bignum (as needed) UINT Integer
fixed64 Всегда восемь байт. Более эффективен, чем uint64, если значение обычно больше 256. UINT64 Long [1] int / long [3] UINT64 TWINS ULONG Integer/string[5]
sfixed32 Всегда четыре байта. INT32 INT INT INT32 Fixnum or Bignum (as needed) INT Integer
sfixed64 Всегда восемь байт. Int64 long int / long [3] Int64 TWINS long Integer/string[5]
Boolean Boolean Boolean Boolean Boolean TrueClass / FalseClass Boolean Boolean
string Строки всегда должны содержать текст в кодировке UTF-8 или 7-битный текст ASCII. string string str / unicode[4] string String (UTF-8) string string
byte Может содержать произвольные последовательности байтов. string Byte string Strait []byte String (ASCII-8BIT) Byte string string

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

[1] В Java беззнаковые 32-битные и 64-битные целые числа представляются с использованием их аналогов со знаком, а старший бит просто сохраняется в бите знака.

[2] Во всех случаях установка значения в поле будет выполнять проверку типа, чтобы убедиться, что оно допустимо.

[3] 64-битное или 32-битное целое число без знака всегда представляется как тип long при декодировании, но может быть целым числом, если при установке поля задано целое число. Во всех случаях значение должно соответствовать типу, представленному при установке. См. [2].

[4] Строки Python представляются как Unicode при декодировании, но могут быть str, если задана строка ASCII (это может отличаться).

[5] Integer предназначен для 64-разрядных компьютеров, а String — для 32-разрядных компьютеров.

По умолчанию

При синтаксическом анализе сообщения, если закодированное сообщение не содержит определенного единственного элемента, соответствующее поле в объекте синтаксического анализа будет установлено на значение по умолчанию для этого поля. Эти значения по умолчанию зависят от типа:

  • Для строк по умолчанию используется пустая строка.
  • Для байтов по умолчанию используются нулевые байты.
  • Для логических значений значение по умолчанию равно false.
  • Для числовых типов значение по умолчанию равно нулю.
  • заперечислить, значение по умолчанию — первоеопределенное значение перечисления,ДолженценностьДолжно быть 0.
  • Для полей сообщения это поле не устанавливается. Его точное значение зависит от языка. Для получения дополнительной информации см.Сгенерированный код относится к

Значение по умолчанию для повторяющихся полей пусто (обычно это пустой список для соответствующего языка).

Обратите внимание, что для скалярных полей сообщения после анализа сообщения нет способа определить, задано ли поле явно значение по умолчанию (например, установлено ли логическое значение).false) или вообще не задавать: это следует учитывать при определении типа сообщения. Например,falseЕсли вы не хотите, чтобы это поведение происходило по умолчанию, не существует логического значения, которое включает какое-либо поведение, если установлено значение . Также обратите внимание, что если отмеченное поле сообщенияодеялоУстановите значение по умолчанию, которое не будет сериализовано по сети.

Дополнительные сведения о том, как значения по умолчанию работают в сгенерированном коде, см.Руководство по созданию кода.

перечислить

При определении типа сообщения вы можете захотеть, чтобы одно из полей имело только один предопределенный список значений. Например, вы хотите добавитьcorpusКаждое полеSearchRequest, где корпус может бытьUNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTSилиVIDEO. Вы можете сделать это очень просто,enumДобавьте константу для каждого возможного значения, чтобы определить определение сообщения.

В приведенном ниже примере мы добавляемenumперечислитьCorpusи поле типаCorpus:

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}

Как вы видете,CorpusПервая константа перечисления сопоставляется с нулем: каждое перечисление определяетдолженСодержит константу, которая сопоставляется с нулем в качестве первого элемента. Это потому что:

  • Должно быть нулевое значение, чтобы мы могли использовать 0 в качестве числаПо умолчанию.
  • Нулевое значение должно быть первым элементом, чтобы соответствоватьproto2Семантически совместимый, где первое значение перечисления всегда является значением по умолчанию.

Вы можете определить псевдонимы, указав одно и то же значение для разных констант перечисления. Для этого вам нужноallow_aliasпараметры установлены наtrue, иначе компилятор протокола выдаст сообщение об ошибке, когда найдет псевдоним.

enum EnumAllowingAlias {
  option allow_alias = true;
  UNKNOWN = 0;
  STARTED = 1;
  RUNNING = 1;
}
enum EnumNotAllowingAlias {
  UNKNOWN = 0;
  STARTED = 1;
  // RUNNING = 1;  // Uncommenting this line will cause a compile error inside Google and a warning message outside.
}

Константы перечислителя должны находиться в диапазоне 32-битных целых чисел. так какenumЗначения используются онлайнвариантное кодирование, поэтому отрицательные значения неэффективны и поэтому не рекомендуются. ты сможешьenumОпределите s в определении сообщения, как в примере выше,enumтакже могут быть определены извне - они могут быть определены в.protoфайл для повторного использования в любых определениях сообщений. Вы также можете использоватьenumСинтаксис использует тип, объявленный в одном сообщении, как тип поля в другом сообщении.*MessageType*.*EnumType*

когда вы.protoВремя выполнения на компиляторе с использованием буфера протоколаenum, сгенерированный код будет иметьenumСоответствующий код на Java или C++, этоEnumDescriptor— это специальный класс Python, используемый для создания набора символических констант с целочисленными значениями в классе, сгенерированном во время выполнения.

При десериализации в сообщении сохраняются нераспознанные значения перечисления, но при десериализации сообщения способ представления таких значений зависит от языка. В языках, поддерживающих открытые типы перечислений со значениями за пределами указанного символьного диапазона, таких как C++ и Go, неизвестные значения перечислений хранятся только как лежащие в их основе целочисленные представления. В языках с перечислимыми типами перечисления (например, в Java) регистр в перечислениях используется для представления нераспознанных значений, а доступ к базовым целым числам можно получить с помощью специальных методов доступа. В любом случае, если сообщение сериализовано, все равно будут использоваться значения, не распознанные сериализацией сообщения.

о том какenumПодробнее об использовании сообщений в приложении см.Руководство по созданию кода.

зарезервированное значение

Если полностью удалить запись перечисления или закомментировать еевозобновитьперечисления, значение может быть повторно использовано будущими пользователями при внесении собственных обновлений типа. Это может вызвать серьезные проблемы, если та же самая старая версия будет загружена позже..proto, включая повреждение данных, ошибки конфиденциальности и т. д. Один из способов предотвратить это — указать числовое значение (и/или имя) удаленной записи, что также может вызвать проблемы с сериализацией JSON.reserved. Компилятор буфера протокола будет жаловаться, если какие-либо будущие пользователи попытаются использовать эти идентификаторы. ты можешь использовать этоmaxКлючевое слово указывает на сохранение диапазона значений до максимально возможного значения.

enum Foo {
  reserved 2, 15, 9 to 11, 40 to max;
  reserved "FOO", "BAR";
}

Обратите внимание, что вы не можетеreservedСмешайте имена полей и значения в инструкции.

Используйте другие типы сообщений

Вы можете использовать другие типы сообщений в качестве типов полей. Например, вы хотите включитьResultкаждого сообщенияSearchResponseсообщение - для этого вы можете определитьResultв том же типе сообщения.proto, затем укажите поле типаResultсерединаSearchResponse:

message SearchResponse {
  repeated Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}

определение импорта

В приведенном выше примереResultТипы сообщений определяются в том же файлеSearchResponse- если тип сообщения, который будет использоваться в качестве типа поля, уже находится в другом.protoопределено в файле, что мне делать?

ты сможешь.protoпройти черезИмпортироватьиспользовать определения из других файлов. импортировать другие.protoОпределение человека, добавьте оператор импорта вверху файла:

import“myproject / other_protos.proto”;

По умолчанию вы можете использовать только прямой импорт.protoопределение в файле. Однако иногда может потребоваться.protoФайл перемещается в новое место..protoТеперь вы можете.protoпоместите фиктивный файл в старое место, чтобы использоватьimport publicКонцепция перенаправляет все импортированные данные в новое место вместо того, чтобы напрямую перемещать файлы и обновлять все сайты вызовов за одно изменение.import publicЛюбой импорт, который содержит этоimport publicВысказывания в прото людей могут транзитивно зависеть от них. Например:

// new.proto
// All definitions are moved here
// old.proto
//This is the proto that all clients are importing.
import public“new.proto”;
import“other.proto”;
// client.proto
import "old.proto";
//您使用old.proto和new.proto中的定义,但不使用other.proto

Компилятор протокола использует-I/ --proto_pathфлаг Искать импортированные файлы в наборе каталогов, указанном в командной строке компилятора протокола. Если флаги не указаны, он будет искать в каталоге, в котором был вызван компилятор. Как правило, вы должны--proto_pathФлаги устанавливаются в корень проекта и используют полные имена для всех импортов.

Использование типов сообщений proto2

можно импортироватьproto2типы сообщений и использовать их в сообщениях proto3 и наоборот. Однако перечисления proto2 нельзя использовать напрямую в синтаксисе proto3 (если импортированные сообщения proto2 используют их, это нормально).

вложенный тип

Вы можете определить и использовать типы сообщений в других типах сообщений, как показано в примере ниже — здесьResultСообщение определяется в сообщенииSearchResponse:

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

Если вы хотите повторно использовать этот тип сообщения за пределами его родительского типа сообщения, вызовите его:*Parent*.*Type*

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}

Вы можете вкладывать сообщения так глубоко, как хотите:

message Outer {                  // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      int64 ival = 1;
      bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      int32 ival = 1;
      bool  booly = 2;
    }
  }
}

Обновить тип сообщения

Если существующий тип сообщения больше не соответствует всем вашим потребностям — например, вы хотите, чтобы в формате сообщения были дополнительные поля, — но вы все еще хотите использовать код, созданный в старом формате, не волнуйтесь! Обновлять типы сообщений без нарушения существующего кода очень просто. Запомните следующие правила:

  • Не изменяйте номера полей любых существующих полей.
  • Если добавляется новое поле, любое сообщение, сериализованное с помощью кода, использующего «старый» формат сообщения, все еще может быть проанализировано вновь сгенерированным кодом. Вы должны запомнить эти элементыПо умолчанию, чтобы новый код мог правильно взаимодействовать с сообщениями, созданными старым кодом. Точно так же сообщения, созданные вашим новым кодом, могут быть проанализированы старым кодом: старый двоичный файл просто игнорирует новые поля при разборе. Для получения дополнительной информации см. "Неизвестное поле"часть
  • Поля можно удалять, если номер поля больше не используется в обновленном типе сообщения. Вы можете переименовать поле, возможно, добавив префикс «OBSOLETE_» илирезервНомер поля для ваших будущих пользователей.protoЭтот номер не используется повторно случайно.
  • int32,uint32,int64,uint64boolвсе совместимы - это означает, что вы можете изменить эти типы на одно поле другого, не нарушая совместимость вперед или назад. Если вы выберете из проводника число, которое не соответствует соответствующему типу, вы получите тот же эффект, что и преобразование числа в этот тип в C++ (например, если вы читаете 64-битное число как int32). , оно будет усечено до 32 бит).
  • sint32иsint64Совместимы друг с другом, но с другими целочисленными типамиНетсовместимый.
  • string``bytesОни совместимы, если байты действительны в кодировке UTF-8.
  • bytesВстроенные сообщения совместимы, если байты содержат закодированную версию сообщения.
  • fixed32совместим сsfixed32fixed64использоватьsfixed64.
  • enumсовместим сint32,uint32,int64uint64Термины формата проводов (обратите внимание, что значения будут усечены, если они не подходят). Однако обратите внимание, что при десериализации сообщений клиентский код может обрабатывать их по-разному: например,enumНераспознанные типы proto3 будут сохранены в сообщении, но способ представления этого типа при десериализации сообщения зависит от языка. Поля Int всегда сохраняют свое значение.
  • Измените одно значение нановыйчленoneofбезопасен и бинарно совместим.oneofЕсли вы уверены, что нет кода для одновременной установки нескольких полей, может быть безопасно переместить несколько полей в новые поля. Переместить любое поле в существующее полеoneofНе безопасно.

неизвестное поле

Неизвестные поля — это правильно сформированные сериализованные данные буфера протокола, представляющие поля, которые не распознаются синтаксическим анализатором. Например, когда старый двоичный файл анализирует данные, отправленные новым двоичным файлом, с новыми полями, эти новые поля становятся неизвестными полями в старом двоичном файле.

Первоначально сообщения proto3 всегда отбрасывали неизвестные поля во время синтаксического анализа, но в версии 3.5 мы вновь ввели сохранение неизвестных полей, чтобы соответствовать поведению proto2. В версии 3.5 и более поздних неизвестные поля сохраняются во время синтаксического анализа и включаются в сериализованный вывод.

любой

ДолженAnyТип сообщения, вы можете использовать почту как встроенный тип без необходимости определять свой собственный .proto. ОдинAnyСодержит произвольные сериализованные сообщенияbytesвместе с URL-адресом, который разрешается в тип сообщения, чтобы действовать как глобальный уникальный идентификатор. использовать этоAnyтип, который вам нуженИмпортироватьgoogle/protobuf/any.proto.

import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  repeated google.protobuf.Any details = 2;
}

URL-адрес типа по умолчанию для данного типа сообщения.type.googleapis.com/*packagename*.*messagename*

Реализации различных языков будут поддерживать вспомогательные библиотеки времени выполнения для упаковки и распаковки любого значения типобезопасным способом — например, в Java любой тип будет иметь специальныеpack()иunpack()доступ, а в C++ естьPackFrom()иUnpackTo()метод:

// Storing an arbitrary message type in Any.
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()->PackFrom(details);

// Reading an arbitrary message from Any.
ErrorStatus status = ...;
for (const Any& detail : status.details()) {
  if (detail.Is<NetworkErrorDetails>()) {
    NetworkErrorDetails network_error;
    detail.UnpackTo(&network_error);
    ... processing network_error ...
  }
}

В настоящее время разрабатывается библиотека времени выполнения для обработки типа Any..

Если вы уже знакомы ссинтаксис прото2, тип Any заменитрасширять.

Oneof

Если у вас есть сообщение со многими полями и одновременно может быть задано не более одного поля, вы можете использовать функцию oneof, чтобы применить это поведение и сэкономить память.

Одно из полей похоже на обычное поле, за исключением того, что все поля находятся в одной общей памяти, и одновременно может быть задано не более одного поля. Установка любого члена oneof автоматически очищает все остальные члены. Вы можете использовать спец.case()илиWhichOneof()Метод проверяет, какое значение (если есть) находится в oneof, в зависимости от выбранного вами языка.

Использование одного из

Чтобы определить один из вас, пожалуйста.protoиспользоватьoneofключевое слово, за которым следует ваше имя, в данном случаеtest_oneof:

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}

Затем добавьте поле oneof в определение oneof. Вы можете добавить любой тип поля, но вы не можете использоватьrepeatedполе.

В сгенерированном коде одно из полей имеет те же геттеры и сеттеры, что и обычные поля. Вы также можете использовать специальные методы для проверки значения в oneof (если оно есть). Вы можете найти соответствующиеСправочник по APIНайдите дополнительную информацию об API oneof для выбранного языка.

одна из особенностей

  • Установка поля oneof автоматически очистит все остальные элементы oneof. Поэтому, если вы установите более одного поля, только то, которое вы установили

    последний

    Поля по-прежнему имеют значения.

    SampleMessage message;
    message.set_name("name");
    CHECK(message.has_name());
    message.mutable_sub_message();   // Will clear name field.
    CHECK(!message.has_name());
    
  • Если синтаксический анализатор встречает несколько членов одного и того же oneof в сети, в анализируемом сообщении используется только последний увиденный элемент.

  • невозможноеrepeated.

  • Reflection API работает на одном из полей.

  • Если вы используете C++, убедитесь, что ваш код не приводит к сбою памяти. Следующий пример кода приведет к сбою,sub_message

    позвонив вset_name()метод удаляет этот код.

    SampleMessage message;
    SubMessage* sub_message = message.mutable_sub_message();
    message.set_name("name");      // Will delete sub_message
    sub_message->set_...            // Crashes here 
    
  • Также в C++, если у вас естьSwap()Два сообщения с oneofs, каждое сообщение завершается другим результатом сообщения: в приведенном ниже примереmsg1будет иметьsub_message,msg2и будет иметьname.

    SampleMessage msg1;
    msg1.set_name("name");
    SampleMessage msg2;
    msg2.mutable_sub_message();
    msg1.swap(&msg2);
    CHECK(msg1.has_sub_message());
    CHECK(msg2.has_name());
    

проблемы с обратной совместимостью

Будьте осторожны при добавлении или удалении одного из этих полей. Если вы проверите значение, возвращаемое одним изNone/ NOT_SET, что может означать, что oneof не был установлен или был установлен в поле в другой версии oneof. Невозможно определить разницу, потому что невозможно узнать, является ли неизвестное поле на проводе одним из членов.

Проблема с повторным использованием ярлыка

  • Перемещение полей в или из одного из: После сериализации и разбора сообщения вы можете потерять часть информации (некоторые поля будут очищены). Однако вы можете безопасно перемещать отдельные поля вновыйoneof и может перемещать несколько полей, если известно, что установлено только одно поле.
  • удалите поле oneof и добавьте его обратно: Это может очистить текущее установленное поле oneof после сериализации и разбора сообщения.
  • разделить или объединить один из: аналогичная проблема с перемещением обычных полей.

карта

Если вы хотите создать ассоциативную карту в определении данных, Protocol Buffers предоставляет удобный синтаксис быстрого доступа:

map < key_type ,value_type > map_field = N ;

...вkey_typeМожет быть любого целочисленного или строкового типа (таким образом, любойскаляртипbytes). Обратите внимание, что перечисления недействительныkey_type. изvalue_typeМожет быть любого типа, кроме другой карты.

Так, например, если вы хотите создать карту проекта, где каждая записьProjectсообщения связаны со строковым ключом, который можно определить следующим образом:

map < string ,Project > projects = 3 ;  
  • поля карты не могутrepeated.
  • Порядок значений карты проводного формата и порядок итерации карты не определены, поэтому вы не можете полагаться на определенный порядок элементов карты.
  • При создании текстового формата для.proto, карта отсортирована по ключу. Цифровые клавиши отсортированы по номерам.
  • При синтаксическом анализе или слиянии со строкой, если есть повторяющиеся ключи карты, используется последний увиденный ключ. При синтаксическом анализе карты из текстового формата синтаксический анализ может завершиться ошибкой, если есть повторяющиеся ключи.
  • Если поле карты снабжено ключом, но не имеет значения, поведение поля при сериализации зависит от языка. В C++, Java и Python значение типа по умолчанию сериализуется, а в других языках сериализация вообще отсутствует.

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

Обратная совместимость

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

message MapFieldEntry {
  key_type key = 1;
  value_type value = 2;
}

repeated MapFieldEntry map_field = N;

Любая реализация буфера протокола, поддерживающая отображение, должна генерировать и принимать данные, приемлемые в соответствии с приведенным выше определением.

Сумка

ты сможешь.protoдобавление файлаpackageНеобязательный спецификатор для предотвращения конфликтов имен между типами протокольных сообщений.

package foo.bar;
message Open { ... }

Затем вы можете использовать спецификатор пакета при определении полей типа сообщения:

message Foo {
  ...
  foo.bar.Open open = 1;
  ...
}

То, как спецификаторы пакета влияют на сгенерированный код, зависит от выбранного вами языка:

  • существуетС++, сгенерированные классы включаются в пространство имен C++. Например,Openбудет в пространстве именfoo::bar.
  • существуетЯва, который используется как пакет Java, если выoption java_packageсуществует.protoПакет явно указан в документации.
  • существуетпитон, директива пакета игнорируется, поскольку модули Python организованы в соответствии с их расположением в файловой системе.
  • существуетв Го, который используется в качестве имени пакета Go, если выoption go_packageсуществует.protoявно указано в документации.
  • существуетРубин, сгенерированный класс содержится во вложенном пространстве имен Ruby, преобразованном в желаемую заглавную букву Ruby (первая буква заглавная; если первый символ не является буквой,PB_предваряется). Например,Openбудет в пространстве именFoo::Bar.
  • существуетС#, пакет преобразуется в PascalCase и используется как пространство имен, если только вы неoption csharp_namespaceсуществует.protoявно указано в документации. Например,Openбудет в пространстве именFoo.Bar.

Пакет и разрешение имени

Разрешение имени типа в языках буфера протокола похоже на C++: сначала просматривается самая внутренняя область, затем следующая область и так далее, каждый пакет считается «внутри» своего родительского пакета. ведущий '. ' (Например,.foo.bar.Baz) означает начало с самой внешней области.

Компилятор protobuf разрешает импортированные.protoфайл для разрешения всех имен типов. Генератор кода для каждого языка знает, как ссылаться на каждый тип в этом языке, даже если у него разные правила области видимости.

определить услугу

Если вы хотите использовать тип сообщения с системой RPC (удаленный вызов процедур), вы можете.protoИнтерфейс службы RPC определен в файле, и компилятор protobuf сгенерирует код интерфейса службы и заглушки на выбранном вами языке. Так, например, если вы хотите определить метод запроса службы RPC как:SearchRequestи метод возврата:SearchResponse,Может.protoОпределите его в файле следующим образом:

service SearchService {
  rpc Search(SearchRequest)returns(SearchResponse);
}

Простейшая система RPC для использования с буферами протоколов — этоgRPC: Независимая от платформы система RPC с открытым исходным кодом, разработанная Google. gRPC особенно хорошо работает с protobuf и позволяет.protoСоответствующий код RPC создается непосредственно в файле с помощью специального подключаемого модуля компилятора protobuf.

Если вы не хотите использовать gRPC, вы также можете использовать protobuf со своей собственной реализацией RPC. ты сможешьВ руководстве по языку Proto2Узнайте больше об этом.

Есть также несколько текущих сторонних проектов, разрабатывающих реализации RPC с использованием протокольных буферов. Для связанного списка элементов, о которых мы знаем, см.Вики-страница сторонних надстроек.

Сопоставление JSON

Proto3 поддерживает каноническое кодирование в JSON, что упрощает обмен данными между системами. Кодировки описаны тип за типом в таблице ниже.

Если в данных, закодированных в формате JSON, отсутствует значениеnull, или его значение, при синтаксическом анализе в буфер протокола оно будет интерпретировано соответствующим образомПо умолчанию. Если поле имеет значение по умолчанию в буфере протокола, оно по умолчанию будет исключено из данных, закодированных в формате JSON, для экономии места. Реализации могут предоставлять опции для генерации полей со значениями по умолчанию в выходных данных в формате JSON.

proto3 JSON Пример JSON Примечания
message object {"fooBar": v, "g": null,…} Сгенерируйте JSON-объект. Имена полей сообщений отображаются в нижнем регистре верблюда и становятся ключами объекта JSON. еслиjson_nameЕсли опция поля указана, указанное значение будет использоваться в качестве ключа. Парсер принимает имена в нижнем регистре (илиjson_nameoption) и оригинальное имя поля proto.nullявляется допустимым значением для всех типов полей и рассматривается как значение по умолчанию для соответствующего типа поля.
eunm String "FOO_BAR" Используйте имя значения перечисления, указанное в proto. Анализатор принимает имена перечислений и целочисленные значения.
карта object {"k": v, …} Все ключи преобразуются в строки.
repeated V. array [v, …] nullПринимается как пустой список[].
bool true,false true, false
string string "Hello World!"
bytes base64 string "YWJjMTIzIT8kKiYoKSctPUB+" Значение JSON будет представлять собой данные, закодированные в виде строки с использованием стандартной кодировки base64 с дополнением. Принимает стандартную или URL-безопасную кодировку base64 с/без заполнения.
int32, фиксированный32, uint32 string 1, -10, 0 Значения JSON будут десятичными числами. Принимает числа или строки.
int64, фиксированный64, uint64 string "1", "-10" Значения JSON будут десятичными строками. Принимает числа или строки.
float,double number 1.1, -10.0, 0, "NaN","Infinity" Значением JSON будет число или специальное строковое значение «NaN», «Infinity» и «-Infinity». Принимает числа или строки. Экспоненциальное представление также принято.
any object {"@type": "url", "f": v, … } Если Any содержит значение со специальным сопоставлением JSON, оно будет преобразовано следующим образом: . В противном случае значение будет преобразовано в объект JSON, а поле будет вставлено для указания фактического типа данных.{"@type": xxx, "value": yyy}``"@type"
Timestamp string "1972-01-01T10:00:20.021Z" Используйте RFC 3339, где результирующий вывод всегда будет нормализован по Z и использует 0, 3, 6 или 9 знаков после запятой. Смещения, отличные от "Z", также принимаются.
Duration string "1.000340012s", "1s" Сгенерированный вывод всегда содержит 0, 3, 6 или 9 знаков после запятой, в зависимости от желаемой точности, за которыми следует суффикс "s". Допускаются любые десятичные знаки (и никакие), если они соответствуют точности в наносекундах и требуется суффикс «s».
Struct object { … } Любой объект JSON. Видеть.struct.proto
Wrapper types various types 2, "2", "foo", true,"true", null, 0, … Оболочка использует то же представление в JSON, что и примитивный тип оболочки, за исключениемnullПредставления разрешены и сохраняются при преобразовании и передаче данных.
FieldMask string "f.fooBar,h" Видеть.field_mask.proto
ListValue array [foo, bar, …]
Value value любое значение JSON
NullValue null JSON null

Параметры JSON

Реализация proto3 JSON может предоставлять следующие параметры:

  • Выдать поля со значениями по умолчанию: опущен по умолчанию в выводе proto3 JSONсо значением по умолчаниюполе. Реализации могут предоставить возможность переопределить это поведение и поля вывода с их значениями по умолчанию.
  • Игнорировать неизвестные поля: по умолчанию анализатор Proto3 JSON должен отклонять неизвестные поля, но можно предоставить возможность игнорировать неизвестные поля при анализе.
  • Используйте прото-имена полей вместо имен нижнего регистра верблюда.: по умолчанию принтер proto3 JSON должен преобразовывать имена полей в нижний регистр и использовать их в качестве имен JSON. Реализации могут предоставлять возможность использовать имена прото-полей в качестве имен JSON. Парсер Proto3 JSON должен принимать преобразованные имена нижнего регистра верблюда и имена полей proto.
  • Отправлять значения перечисления как целые числа вместо строк: по умолчанию имя значения перечисления используется в выводе JSON. Могут быть предоставлены опции для использования числового значения значения перечисления.

опции

.protoОтдельные объявления в файле могут использовать множествоопциикомментировать. Параметры не меняют общего значения объявления, но могут повлиять на то, как оно обрабатывается в определенном контексте. Полный список доступных опций указан вgoogle/protobuf/descriptor.proto.

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

Вот некоторые из наиболее распространенных вариантов:

  • java_package(опция файла): пакет, используемый для сгенерированных классов Java. если.protoВ файле не указаны явные параметрыjava_package, пакет proto будет использоваться по умолчанию (укажите .proto с помощью ключевого слова «package» в файле). Однако прото-пакеты обычно не создают хороших пакетов Java, потому что прото-пакеты не начинаются с обратного доменного имени. Эта опция не действует, если код Java не сгенерирован.

    option java_package =“com.example.foo”;
    
  • java_multiple_files(параметр файла): вызывает определение сообщений, перечислений и служб верхнего уровня на уровне пакета, а не во внешних классах, названных в честь файла .proto.

option java_multiple_files = true;
  • java_outer_classname(опция файла): имя класса самого внешнего класса Java (и имя файла), который необходимо сгенерировать. если.protoв файле не указаноjava_outer_classname, то, положив.protoимена файлов преобразуются в верблюжий регистр (поэтомуfoo_bar.protoстатьFooBar.java) для создания имени класса. Эта опция не действует, если код Java не сгенерирован.
  option java_outer_classname =“Ponycopter”;
  • optimize_for

    (параметры файла): можно установитьSPEED,CODE_SIZEилиLITE_RUNTIME. Это влияет на генераторы кода C++ и Java (и, возможно, сторонние генераторы) следующим образом:

    • SPEED(по умолчанию): компилятор protobuf будет генерировать код для сериализации, разбора и выполнения других распространенных операций над типами сообщений. Этот код очень оптимизирован.
    • CODE_SIZE: Компилятор protobuf будет генерировать минимальные классы и полагаться на общий код на основе отражения для сериализации, синтаксического анализа и различных других операций. Таким образом, сгенерированный код лучше, чем использованиеSPEEDГораздо меньше, но операция будет медленнее. Класс по-прежнему будет реализовывать то же самоеSPEEDСхема точно такая же, как и у общедоступного API. Этот режим содержит очень большое количество.protoНаиболее полезен при применении документов и не требует, чтобы все документы были очень быстрыми.
    • LITE_RUNTIME: Компилятор protobuf сгенерирует библиотеку времени выполнения, которая зависит только от «облегченной» (libprotobuf-liteвместоlibprotobuf) тип. Среда выполнения Lite намного меньше (примерно на порядок меньше), чем вся библиотека, но в ней отсутствуют определенные функции, такие как дескрипторы и отражение. Это особенно полезно для приложений, работающих на ограниченных платформах, таких как мобильные телефоны. Компилятор по-прежнему будет выглядеть как вSPEEDГенерирует быстрые реализации всех методов, как в схеме. Сгенерированный класс будет реализовывать толькоMessageLiteИнтерфейс для каждого языка, который обеспечивает только полноеMessageПодмножество методов интерфейса.
    option optimize_for = CODE_SIZE;
    
    
  • cc_enable_arenas(опция файла): включить для сгенерированного кода C++Распределение арены.

  • objc_class_prefix(параметры файла): устанавливает префикс класса Objective-C, который добавляется ко всем сгенерированным классам и перечислениям Objective-C для этого .proto. Там нет значения по умолчанию. вы должны использоватьApple предложилПрефикс из 3-5 символов верхнего регистра. Обратите внимание, что Apple резервирует все двухбуквенные префиксы.

  • deprecated(параметры поля): если установленоtrue, это поле устарело и не должно использоваться в новом коде. В большинстве языков это не имеет практического значения. В Java это становится@DeprecatedПримечания. В будущем генераторы кода для других языков могут генерировать устаревшие комментарии к методам доступа к полю, что вызовет предупреждение при компиляции кода, пытающегося использовать это поле. Если поле никем не используется и вы хотите запретить его использование новыми пользователями, рассмотрите возможность замены объявления поля зарезервированным оператором.

    int32 old_field = 6 [deprecated = true];
    
    

пользовательские параметры

Буферы протоколов также позволяют вам определять и использовать свои собственные параметры. Это то, что большинству людей не нужноРасширенные возможности. Если вы считаете, что вам нужно создать свои собственные параметры, см.Руководство по языку Proto2для деталей. Обратите внимание, что для создания пользовательских параметров используетсяимя расширенияРазрешено только для пользовательских параметров в proto3.

создать свой класс

В соответствии с фактическими рабочими потребностями создавайте собственные типы сообщений на следующих языках: Java, Python, C++, Go, Ruby, Objective-C или C#..protoфайл, вам нужно запустить компилятор protobufprotocначальство.proto. Если компилятор еще не установлен, пожалуйстаскачать пакети следуйте инструкциям в файле readme. Для Go также необходимо установить специальный плагин генератора кода для компилятора: его можно найти на GitHub.golang / protobufНайдите это и инструкции по установке в репозитории.

Компилятор Protobuf вызывается следующим образом:

protoc --proto_path = IMPORT_PATH --cpp_out = DST_DIR --java_out = DST_DIR --python_out = DST_DIR --go_out = DST_DIR --ruby_out = DST_DIR --objc_out = DST_DIR --csharp_out = DST_DIR  path / to / file .proto

  • IMPORT_PATHуточнить.protoРазобратьimportКаталог, в котором искать файлы при директиве. Если опущено, используется текущий каталог. в состоянии пройти--proto_pathПередайте параметр несколько раз, чтобы указать несколько каталогов импорта; они будут искаться по порядку. Может использоваться как краткая форма.-I=*IMPORT_PATH*``--proto_path

  • Вы можете указать одну или несколько выходных директив:

    Для удобства, если DST_DIR заканчивается на.zipили.jar, компилятор запишет вывод в один архивный файл формата ZIP с заданным именем..jarНа выходе также будет предоставлен файл манифеста, как того требует спецификация Java JAR. Учтите, что если выходной архив уже существует, он будет перезаписан, компилятор не настолько умен, чтобы добавлять файлы в существующий архив.

  • Вы должны предоставить один или несколько.protoфайл в качестве входных данных..protoНесколько файлов могут быть указаны одновременно. Хотя имена файлов относятся к текущему каталогу, каждый файл должен находиться в одном из файлов,IMPORT_PATHчтобы компилятор мог определить его каноническое имя.