protobuf就是一种数据格式,如json,xml等
1.安装proto工具,并copy到path/bin下 https://github.com/protocolbuffers/protobuf/releases
2.go get -u github.com/golang/protobuf/protoc-gen-go
protoc-gen-go在两个地方都有https://github.com/golang/protobuf/tree/master/protoc-gen-gohttps://github.com/protocolbuffers/protobuf-go/tree/master/cmd/protoc-gen-go

protoc 和 protoc-gen-go 产生的 proto 文件代码对比
$ protoc —go_out=./go/ ./proto/helloworld.proto
$ protoc —go_out=plugins=grpc:./go2/ ./proto/helloworld.proto
上面两个命令,第一个产生的只是 protobuffer 文件序列化和反序列化的代码。
第二个产生的则除了第一个的代码外,还增加服务器和客户端通讯、实现的公共库代码。
走通流程
1.编写proto文件, person.proto
syntax = "proto3"; //首行声明使用的protobuf版本为proto3package serialize; // 声明所在包// package 项目名.模块名; // 常见的命名option go_package = "domain.com/projectName/serialize;proto"; // 生成的go文件包名// message person 会生成 Person 命名的结构体message person {int32 id = 1;string name = 2;}// message all_person 会按照驼峰规则自动生成名为AllPerson 的结构体message all_person {repeated person Per = 1;}
//--------------枚举-----------------------一般的枚举的第一个字段使用enum PhoneType {UNSPECIFIED = 0; // 未使用,第一个必须是0MOBILE = 1;}编译为go文件就是type PhoneType int32const (PhoneType_UNSPECIFIED PhoneType = 0 // 未使用,第一个必须是0PhoneType_MOBILE PhoneType = 1 //第一个必须是0)func (x Order_State) Enum() *Order_State {p := new(Order_State)*p = xreturn p}func (x PhoneType) String() string {return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))}func (PhoneType) Descriptor() protoreflect.EnumDescriptor {return file_order_proto_order_proto_enumTypes[4].Descriptor()}func (PhoneType) Type() protoreflect.EnumType {return &file_order_proto_order_proto_enumTypes[4]}func (x PhoneType) Number() protoreflect.EnumNumber {return protoreflect.EnumNumber(x)}// Deprecated: Use Order_State.Descriptor instead.func (PhoneType) EnumDescriptor() ([]byte, []int) {return file_order_proto_order_proto_rawDescGZIP(), []int{1, 1}}// 枚举类型还会生成两个字典var (PhoneType_name = map[int32]string{0: "UNSPECIFIED",1: "MOBILE",}PhoneType_value = map[string]int32{"UNSPECIFIED": 0,"MOBILE": 1,})-----------------------------------------------message MyMessage1 {enum EnumAllowingAlias {option allow_alias = true; // 如果枚举有相同的值,设置allow_alias = true,否则报错UNKNOWN = 0;STARTED = 1;RUNNING = 1;}}type MyMessage1_EnumAllowingAlias int32const (MyMessage1_UNKNOWN MyMessage1_EnumAllowingAlias = 0MyMessage1_STARTED MyMessage1_EnumAllowingAlias = 1MyMessage1_RUNNING MyMessage1_EnumAllowingAlias = 1)//--------------嵌套结构体-----------------------如果要引用内部类,则通过parent.type方式来调用message Outer { // Level 0message MiddleAA { // Level 1message Inner { // Level 2required int64 ival = 1;optional bool booly = 2;}}message MiddleBB { // Level 1message Inner { // Level 2required int32 ival = 1;}}}//--------------Maps-----------------------map<key_type, value_type> map_field = N;key_type可以是除浮点指针或bytes外的其他基本类型,value_type可以是任意类型map<string, Project> projects = 3;map 类型的字段不可重复(不能用 repeated 修饰)//--------------reserved---------------------message Foo {reserved 2, 15, 9 to 11;reserved "foo", "bar";}如果通过完全删除某个字段或对其进行注释来更新消息类型,则将来的用户可以在对该类型进行自己的更新时重用该字段编号。如果以后加载相同.proto的旧版本,这可能会导致严重问题,包括数据损坏、隐私漏洞等。确保不会发生这种情况的一种方法是指定保留已删除字段的字段号(和/或名称,这也可能导致JSON序列化问题)。//--------------deprecated--------------------- 弃用message FooResponse {repeated Order orders = 1 [ deprecated = true ];int32 id = 2;}//--------------json_name---------------------message FooResponse {repeated Order orders = 1 [ json_name = "Order" ];int32 id = 2;}
- 进入proto 目录,使用 protoc 编译 person.proto
- protoc —go_out=. person.proto
- 执行完后会生成一个 person.pb.go 文件
3.使用
package mainimport ("fmt""github.com/golang/protobuf/proto""io/ioutil""study/grpc/protobuf/serializa")func main() {var p1 serialize.Personp1.Id=1p1.Name="tom"var p2 serialize.Personp2.Id=2p2.Name="jack"all_p := serialize.AllPerson{Per:[]*serialize.Person{&p1,&p2}}// 对数据进行序列化//注意参数一定要是指针类型,AllPerson实现了message接口,//在message内部就拿不到AllPerson的地址了data,err :=proto.Marshal(&all_p)if err !=nil{fmt.Println("error:",err.Error())return}ioutil.WriteFile("proto.dat",data,0777)data2,err :=ioutil.ReadFile("proto.dat")if err !=nil{fmt.Println("error:",err.Error())return}// 对数据进行反序列化var tag serialize.AllPersonproto.Unmarshal(data2,&tag)for _,v:=range tag.Per{fmt.Println(v)}}//---------------------------------id:1 name:"tom"id:2 name:"jack"
生成的proto.dat
数据类型

语法
syntax = "proto3";message SearchRequest {string query = 1;int32 page_number = 2;int32 result_per_page = 3;}
- 语法说明(syntax)前只能是空行或者注释
- 每个字段由【字段限制】、字段类型、字段名和编号四部分组成
字段限制
required:必须赋值的字段,proto3已不支持optional:可有可无的字段,改名为 “singular”,默认就是这个,不能显示的添加该限制repeated:可重复字段
编号
- 身份编号,1-2^{29}-1,但不能使用19000到19999之间的值
导包
import搜索路径, 在使用protoc编译时,需要使用选项-I或—proto_path通知protoc去什么地方查找import的文件,如果不指定,protoc将会在当前目录(即调用protoc的路径)下查找。
import "xx/xx/xx.proto":导入其他的包,这样你就可以直接使用其他的包的数据结构例如:xx/xx/a.proto 定义了message AAA在xx/xx/b.proto 中可直接使用a.AAA如果b.proto 与a.proto的package一致,在b.proto 直接使用AAA(而不是a.AAA)
注意:例如现在有3个文件:a,b,c,b导入了a,c导入了b,c是不能拿到a的数据结构,解决:b再导入a时,使用 import public “a.proto” 就可以了
protoc参数
Usage: protoc [OPTION] PROTO_FILES
- -I 如果多个proto文件之间有互相依赖,生成某个proto文件时,需要import其他几个proto文件,这时候就要用-I来指定搜索目录,如果没有指定,则在当前目录将座位搜索目录
- —cpp_out=OUT_DIR Generate C++ header and source.
- —csharp_out=OUT_DIR Generate C# source file.
- —java_out=OUT_DIR Generate Java source file.
- —javanano_out=OUT_DIR Generate Java Nano source file.
- —js_out=OUT_DIR Generate JavaScript source.
- —objc_out=OUT_DIR Generate Objective C header and source.
- —python_out=OUT_DIR Generate Python source file.
- —ruby_out=OUT_DIR Generate Ruby source file.
举例
项目结构:
AAAAA
common
common.proto
common.pb.go
pubplan
pubplan.proto
cd 到AAAAA的上一级目录 执行:
- protoc AAAAA/pubplan/pubplan.proto —go_out=plugins=grpc,paths=source_relative:.
- 会在AAAAA/pubplan下生成go文件,并且
common.proto
syntax = "proto3";package common;//ssh直连信息message SshInfoStruct {string User = 1;string PassWord = 2;string Host = 3;int64 Port = 4;string KeyPath = 5;}// 通用请求体message Request {string cmd = 1;SshInfoStruct info = 2;}
pubplan.proto
syntax = "proto3";package pubplan;import "AAAAA/common/common.proto";message PlanAppPubReq{common.SshInfoStruct info = 1;string appName = 2;string middlewareName = 3;int64 pubMode = 4;string extParam = 5;string targetPath = 6;string serverNode = 7;string ecsTempPath = 8;string ecsBakPath = 9;string fileName = 10;string appProcessName = 11;bool isRepair = 12;}
其它
Any
Any消息类型允许您将消息作为嵌入类型使用,而无需使用它们的.proto定义。Any包含一个以字节表示的任意序列化消息,以及一个URL,该URL充当该消息的全局唯一标识符并解析为该消息的类型。
import "google/protobuf/any.proto";message Response {uint32 Code = 1;string Msg = 2;google.protobuf.Any data = 3;}
生成的结构体
type Response struct {state protoimpl.MessageStatesizeCache protoimpl.SizeCacheunknownFields protoimpl.UnknownFieldsCode uint32 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"`Msg string `protobuf:"bytes,2,opt,name=Msg,proto3" json:"Msg,omitempty"`Data *any.Any `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"`}
测试
package mainimport ("fmt""google.golang.org/protobuf/proto""google.golang.org/protobuf/types/known/anypb"myprto "commons/grpc2/proto")func main() {marshal := &myprto.TestAny{Id: 1,Title: "标题",Content: "内容",}any, err := anypb.New(marshal)fmt.Println(any, err) // [type.googleapis.com/rpc.TestAny]:{Id:1 Title:"标题" Content:"内容"} <nil>msg := &myprto.Response{Code: 0,Msg: "success",Data: any,}fmt.Println(msg) // Msg:"success" data:{[type.googleapis.com/rpc.TestAny]:{Id:1 Title:"标题" Content:"内容"}}unmarshal := &myprto.TestAny{}err = anypb.UnmarshalTo(msg.Data, unmarshal, proto.UnmarshalOptions{})fmt.Println(unmarshal, err) // Id:1 Title:"标题" Content:"内容" <nil>}
调整默认tag
- protoc-go-inject-tag: https://github.com/favadi/protoc-go-inject-tag
- 安装:go get github.com/favadi/protoc-go-inject-tag
这个库可以再proto文件中注入tag,然后在导出的时候相应的字段的tag就可以被修改掉了。
message MyMessage {// @inject_tag: json:"Code"int64 Code = 1;}
生成普通的存根文件后,再执行
protoc-go-inject-tag -input=./test.pb.go
生成的结构体
type MyMessage struct {state protoimpl.MessageStatesizeCache protoimpl.SizeCacheunknownFields protoimpl.UnknownFields// @inject_tag: json:"Code"Code int64 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code"`}
extend
proto3 不支持extension,只支持option, option传值设置:
https://developers.google.com/protocol-buffers/docs/proto3#options
具体option可以分为:
- FileOptions
- MessageOptions
- FieldOptions
- OneofOptions
- EnumOptions
- EnumValueOptions
- ServiceOptions
- MethodOptions
- UninterpretedOption
详见:https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/descriptor.proto
作用:见字意为一个扩展,详细的可以理解是为结构体添加一个或多个字段,但是不能显示的获取或设置值。下面为MessageOptions的一个例子
test.proto
syntax = "proto3";package protobuf;import "google/protobuf/descriptor.proto";option go_package = "protobuf;protobuf";// extend 编号范围为50000-99999extend google.protobuf.MessageOptions {string AAA = 50000 ;uint64 BBB = 50001 ;}
test1.proto
syntax = "proto3";package protobuf;import "grpc/protobuf/test.proto";message MyPerson {option (AAA) = "aaa";string BBB = 1;}
拿到option字段:
package mainimport (pb "commons/grpc/protobuf""google.golang.org/protobuf/descriptor""google.golang.org/protobuf/proto""log")func main() {p := pb.MyPerson{}_, pd := descriptor.MessageDescriptorProto(&p)if v, err := proto.GetExtension(pd.Options, pb.E_AAA); err == nil {log.Printf("GetExtensions(pf.Options, pb.E_AAA) file_opt1:%v", *(v.(*string)))} else {log.Fatalf("GetExtensions(pf.Options, pb.E_AAA) failed: %v", err)}newv := "aaa_reset"if err := proto.SetExtension(pd.Options, pb.E_AAA, &newv); err != nil {log.Fatalf("SetExtension(pf.Options, pb.E_AAA) failed: %v", err)}if v, err := proto.GetExtension(pd.Options, pb.E_AAA); err == nil {log.Printf("GetExtensions(pf.Options, pb.E_AAA) file_opt1:%v", *(v.(*string)))} else {log.Fatalf("GetExtensions(pf.Options, pb.E_AAA) failed: %v", err)}}2021/02/28 22:38:30 GetExtensions(pf.Options, pb.E_AAA) file_opt1:aaa2021/02/28 22:38:30 GetExtensions(pf.Options, pb.E_AAA) file_opt1:aaa_reset
JSON 映射
Proto3 支持JSON的编码规范,使他更容易在不同系统之间共享数据,在下表中逐个描述类型。
如果JSON编码的数据丢失或者其本身就是null,这个数据会在解析成protocol buffer的时候被表示成默认值。如果一个字段在protocol buffer中表示为默认值,体会在转化成JSON的时候编码的时候忽略掉以节省空间。具体实现可以提供在JSON编码中可选的默认值。
| proto3 | JSON | JSON示例 | 注意 |
|---|---|---|---|
| message | object | {“fBar”: v, “g”: null, …} | 产生JSON对象,消息字段名可以被映射成lowerCamelCase形式,并且成为JSON对象键,null被接受并成为对应字段的默认值 |
| enum | string | “FOO_BAR” | 枚举值的名字在proto文件中被指定 |
| map | object | {“k”: v, …} | 所有的键都被转换成string |
| repeated V | array | [v, …] | null被视为空列表 |
| bool | true, false | true, false | |
| string | string | “Hello World!” | |
| bytes | base64 string | “YWJjMTIzIT8kKiYoKSctPUB+” | |
| int32, fixed32, uint32 | number | 1, -10, 0 | JSON值会是一个十进制数,数值型或者string类型都会接受 |
| int64, fixed64, uint64 | string | “1”, “-10” | JSON值会是一个十进制数,数值型或者string类型都会接受 |
| float, double | number | 1.1, -10.0, 0, “NaN”, “Infinity” | JSON值会是一个数字或者一个指定的字符串如”NaN”,”infinity”或者”-Infinity”,数值型或者字符串都是可接受的,指数符号也可以接受 |
| Any | object | {“@type”: “url”, “f”: v, … } | 如果一个Any保留一个特上述的JSON映射,则它会转换成一个如下形式:{“@type”: xxx, “value”: yyy}否则,该值会被转换成一个JSON对象,@type字段会被插入所指定的确定的值 |
| Timestamp | string | “1972-01-01T10:00:20.021Z” | 使用RFC 339,其中生成的输出将始终是Z-归一化啊的,并且使用0,3,6或者9位小数 |
| Duration | string | “1.000340012s”, “1s” | 生成的输出总是0,3,6或者9位小数,具体依赖于所需要的精度,接受所有可以转换为纳秒级的精度 |
| Struct | object | { … } | 任意的JSON对象,见struct.proto |
| Wrapper types | various types | 2, “2”, “foo”, true, “true”, null, 0, … | 包装器在JSON中的表示方式类似于基本类型,但是允许nulll,并且在转换的过程中保留null |
| FieldMask | string | “f.fooBar,h” | 见fieldmask.proto |
| ListValue | array | [foo, bar, …] | |
| Value | value | 任意JSON值 | |
| NullValue | null | JSON null |
any的例子
package google.profile;message Person {string first_name = 1;string last_name = 2;}{"@type": "type.googleapis.com/google.profile.Person","firstName": <string>,"lastName": <string>}
引入 google 定义的 pb 出错
先将protobuf 下载后放 gopath src 下
再配置 goland
