proto3 语法入门

968 阅读5分钟

proto3语法入门

语法指南(需要运用魔法)

定义消息类型

syntax = "proto3";

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

第一行是 protobuf 的版本, 我们主要讲下 message 的定义语法:

message  <message_name> {
  <filed_rule>  <filed_type> <filed_name> = <field_number> 
                    类型         名称             编号  
}
  • message_name: 同一个 pkg 内,必须唯一
  • filed_rule: 可以没有, 常用的有 repeated, oneof
  • filed_type: 数据类型, protobuf 定义的数据类型, 生产代码的会映射成对应语言的数据类型
  • filed_name: 字段名称, 同一个message 内必须唯一。 也就是字段的编号
  • field_number: 字段的编号, 序列化成二进制数据时的字段编号, 同一个message 内必须唯一, 1 ~ 15 使用1个Byte表示, 16 ~ 2047 使用2个Byte表示

如果你想保留一个编号,以备后来使用可以使用 reserved 关键字声明

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

Value(Field) Types

protobuf 定义了很多Value Types, 他和其他语言的映射关系如下:

 

上面就是所有的protobuf基础类型, 光有这些基础类型是不够的, 下面是protobuf为我们提供的一些复合类型

本文章目录结构

image.png

枚举类型

使用enum来声明枚举类型:

enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
}

枚举声明语法:

enum <enum_name> {
    <element_name> = <element_number>
}
  • enum_name: 枚举名称
  • element_name: pkg内全局唯一, 很重要
  • element_number: 必须从0开始, 0表示类型的默认值, 32-bit integer

别名

一个数值可以对应多个枚举值,必须标明option allow_alias = true;

  enum EnumAllowingAlias {
    option allow_alias = true;
    UNKNOWN = 0;
    STARTED = 1;
    RUNNING = 1;
  }

预留值

同理枚举也支持预留值

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

数组类型

如果我们想声明: []string, []Item 这在数组类型怎么办? filed_rule: repeated 可以胜任

message Blog {
  string Value = 1
}
message BlogSet {
  repeated Blog items = 1;
}

// 会编译为:
// type BlogSet BlogSet {
//    items []*BLog
// }

Map

如果我们想声明一个map, 可以如下进行:

map<string, Project> projects = 3;
// projects map[string, Project]

protobuf 声明map的语法:

map<key_type, value_type> map_field = N;

Oneof

很像范型 比如 test_oneof 字段的类型 必须是 sub1 和 sub2 其中之一:

message Sub1 {
    string name = 1;
}

message Sub2 {
    string name = 1;
}

message SampleMessage {
    oneof test_oneof {
        Sub1 sub1 = 1;
        Sub2 sub2 = 2;
    }
}

编译过后结构体

  • isSampleMessage_TestOneof是一个接口, 而sub1和sub2实现了该接口
    • is:必须要
    • SampleMessage:结构体名字
    • TestOneof:字段名
type SampleMessage struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	// Types that are assignable to TestOneof:
	//	*SampleMessage_Sub1
	//	*SampleMessage_Sub2
	TestOneof isSampleMessage_TestOneof `protobuf_oneof:"test_oneof"`
}

那我们如何使用喃:

of := &pb.SampleMessage{}
of.GetSub1()
of.GetSub2()

Any

由于Any是在其他包内声明的,因此在引用它之前需要将该包导入
我们把之前下载的protoc-21.9/include 文件夹放在 git/usrlocal文件夹下,没有 local 文件夹自己建一个
当我们无法明确定义数据类型的时候, 可以使用Any表示:

// 这里是应用其他的proto文件, 后面会讲 ipmort用法
// 首先是要找到文件,是在安装了protobuf后的inlculde里面: /usr/local/include/google/protobuf/any.proto
// 怎么protoc也找到该文: -I=/usr/local/include
// 在该目录下:/usr/local/include, 寻找google/protobuf/any.proto -->/usr/local/include/google/protobuf/any.proto
import "google/protobuf/any.proto";

message ErrorStatus{
    string message = 1;
    google.protobuf.Any data = 2;
}
protoc -I=. -I=/usr/local/include --go_out=./sample --go_opt=module="micro/protobuf/sample" sample/any.proto

any本质上就是一个bytes数据结构

type ErrorStatus struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

	Message string     `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
	Data    *anypb.Any `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
}

下面是 any的定义

// `Any` contains an arbitrary serialized protocol buffer message along with a
// URL that describes the type of the serialized message.
//
// Protobuf library provides support to pack/unpack Any values in the form
// of utility functions or additional generated methods of the Any type.
//  Example 4: Pack and unpack a message in Go
//
//      foo := &pb.Foo{...}
//      any, err := anypb.New(foo)
//      if err != nil {
//        ...
//      }
//      ...
//      foo := &pb.Foo{}
//      if err := any.UnmarshalTo(foo); err != nil {
//        ...
//      }
//
// The pack methods provided by protobuf library will by default use
// 'type.googleapis.com/full.type.name' as the type URL and the unpack
// methods only use the fully qualified type name after the last '/'
// in the type URL, for example "foo.bar.com/x/y.z" will yield type
// name "y.z".
//
//
// JSON
// ====
// The JSON representation of an `Any` value uses the regular
// representation of the deserialized, embedded message, with an
// additional field `@type` which contains the type URL. Example:
//
//     package google.profile;
//     message Person {
//       string first_name = 1;
//       string last_name = 2;
//     }
//
//     {
//       "@type": "type.googleapis.com/google.profile.Person",
//       "firstName": <string>,
//       "lastName": <string>
//     }
//
// If the embedded message type is well-known and has a custom JSON
// representation, that representation will be embedded adding a field
// `value` which holds the custom JSON in addition to the `@type`
// field. Example (for message [google.protobuf.Duration][]):
//
//     {
//       "@type": "type.googleapis.com/google.protobuf.Duration",
//       "value": "1.212s"
//     }
//
type Any struct {
	state         protoimpl.MessageState
	sizeCache     protoimpl.SizeCache
	unknownFields protoimpl.UnknownFields

  ...
	// Note: this functionality is not currently available in the official
	// protobuf release, and it is not used for type URLs beginning with
	// type.googleapis.com.
	//
	// Schemes other than `http`, `https` (or the empty scheme) might be
	// used with implementation specific semantics.
	//
	TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl,proto3" json:"type_url,omitempty"`
	// Must be a valid serialized protocol buffer of the above specified type.
	Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
}

类型嵌套

我们可以再message里面嵌套message

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;
    }
  }
}

与Go结构体嵌套一样, 但是不允许 匿名嵌套, 必须指定字段名称

引用包

// 这里是应用其他的proto文件, 后面会讲 ipmort用法
import "google/protobuf/any.proto";

上面这在情况就是读取的标准库, 我们在安装protoc的时候, 已经把该文件夹的include 挪到usr/local/include下面了,所以可以找到

如果我们proto文件并没有在/usr/local/include目录下, 我们如何导入,比如:
我们在 sample 文件夹下新建 import 文件夹内新建 import.proto

syntax = "proto3";
package sample;
option go_package="micro/protobuf/sample/import";

import "sample/repeated.proto";

message impt{
    sample.BlogSet set = 1;
}
protoc -I=. -I=/usr/local/include --go_out=./sample/import --go_opt=module="micro/protobuf/sample/import" sample/import/import.proto

由于我们使用的import是相对路径, 因此我们必须在 protobuf 下编译, 这样编译器根据相对位置 才能找到这个引用