三.Proto语法

文件头

1
2
3
syntax = "proto3”;
package net;
import "google/protobuf/any.proto";
  • syntax 指定 proto 版本类型;如果不指定,默认使用proto2;如果指定,则必须在文件的非空非注释的第一行;
  • package 包管理,防止不同的消息类型产生命名冲突
  • 通过import 导入其他 .proto 文件

Message类型

定义消息类型

  • 消息类型

    1
    2
    3
    4
    message Param {
    string key = 1; // 参数key
    string value = 2; // 参数value
    }
  • 嵌套消息类型

    1
    2
    3
    4
    5
    message request {
    string url = 1; // 请求路由
    string method = 2; // 请求方法
    repeated Param params = 3; // 请求参数列表
    }

消息类型的字段可以用标量类型定义(如string,int32等),也可以用其他消息类型(如Param)来定义

字段格式

限定修饰符① | 数据类型② | 字段名称③ | = | 字段编码值④ | [字段默认值⑤]

1
2
3
message request {
required string url = 1 [default="http://127.0.0.1:8000"]; // 请求路由
}

1. 限量修饰符

  • required: 表示是一个必须字段,在发送消息前必须设置该字段的值
  • optional: 表示是一个可选字段,可设置可不设置。若发送端未设置该值,则接收端在解析时采用默认值。对于默认值,如果已设置默认值,则采用默认值,如果未设置默认值,则使用类型特定的默认值
  • repeated: 表示是一个重复字段,字段可包含0-N个值,类似数组的含义;属性与optional相同,可选。

    注:proto3版本中取消了required/optional字段的使用,仅保留repeated。在proto3中,所有字段都是可选的。

2. 数据类型

  • 标量类型:
    标量类型
  • 其他消息类型

3. 字段名称

  • 字段名称的命名与C、C++、Java等语言的变量命名方式几乎是相同的。
  • protobuf建议字段的命名采用以下划线分割的驼峰式。例如 first_name 而不是firstName.

4. 字段编码值

数据在序列和反序列的时候,通信双方是如何识别对方的字段的呢?靠的是字段名么?并不是,靠的是这个字段编码值。

.proto文件:

1
2
3
4
5
6
7
8
9
10
11
12
syntax = "proto3";
package test;

message Param {
string key = 1; // 参数key
string value = 2; // 参数value
}

message Param1 {
string key1 = 1; // 参数key
string value2 = 2; // 参数value
}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"github.com/golang/protobuf/proto"
"go-proto/protos"
)

func main () {
param1 := &test.Param{
Key: "page_size",
Value: "1",
}

data, err := proto.Marshal(param1)
if err != nil {
fmt.Println("error: ", err)
}
fmt.Println("data: ", data)

param2 := &test.Param1{}
proto.Unmarshal(data, param2)
fmt.Println("param2: ", param2)
}

运行结果:

1
2
data:  [10 9 112 97 103 101 95 115 105 122 101 18 1 49]
param2: key1:"page_size" value2:"1"

可以看到尽管Param和Param1的字段名不同,但它们依旧可以互相通信。

  • 为了能够相互序列及反序列化,相同的编码值,其限定修饰词和数据类型必须相同
  • 编码值的取值范围为 1~2^32(4294967296)。
  • 其中 1~15的编码时间和空间效率都是最高的,编码值越大,其编码的时间和空间效率就越低
  • 1900~2000编码值为Google protobuf 系统内部保留值,建议不要在自己的项目中使用。
  • protobuf 还建议把经常要传递的值把其字段编码设置为1-15之间的值。
  • 消息中的字段的编码值无需连续,只要是合法的,但是在同一个消息类型中编码值必须唯一

5. 字段默认值

  • 如果设置了默认值,如果在字段没有设置值,则使用此默认值;否则使用字段特定的默认值
  • proto3中取消了字段默认值

枚举类型

1
2
3
4
5
6
7
8
9
10
11
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.
}
  • 枚举值必须大于等于0(proto3中第一个值必须为0)
  • proto2的枚举类型不能被proto3直接import,但是间接引用不受影响。
  • 通过设置可选参数allow_alias为true,就可以在枚举结构中使用别名(两个值元素值相同)

服务类型

rpc服务:

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