protobuf学习笔记
Posted Demonwuwen
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了protobuf学习笔记相关的知识,希望对你有一定的参考价值。
简介
Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
- protobuf是类似与json一样的数据描述语言(数据格式)
- protobuf非常适合于RPC数据交换格式
优缺点
- 优势:
1:序列化后体积相比Json和XML很小,适合网络传输
2:支持跨平台多语言
3:消息格式升级和兼容性还不错
4:序列化反序列化速度很快,快于Json的处理速度 - 缺点:
1:应用不够广(相比xml和json)
2:二进制格式导致可读性差
3:缺乏自描述
整体特点可用如下图所示:
性能比较
参考哔哩哔哩视频数据:
安装
方法 1
mac 笔记本直接brew install protobuf即可.
在命令protoc --go_out=./ *.proto
编译.proto文件到go时,如果出现错误:
protoc-gen-go: program not found or is not executable Please specify a program using absolute path or make sure the program is available in your PATH system variable --go_out: protoc-gen-go: Plugin failed with status code 1.
则说明是protoc-gen-go没装好,再通过以下命令安装即可
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
再次执行命令编译如果出现错误
Please specify either:
• a "go_package" option in the .proto source file, or
• a "M" argument on the command line.
See https://developers.google.com/protocol-buffers/docs/reference/go-generated#package for more information.
--go_out: protoc-gen-go: Plugin failed with status code 1.
原因:因为找不到路径
解决办法:
在.proto文件中一定不要忘了加option go_package =“go/src/microtest/proto”;
go/src/microtest/proto这个是项目的绝对路径,不加的话因为找不到路径所以报了上面的错,如果就想输出到当前proto所在目录,只需要加入option go_package = "./";
即可。
方法 2
-
下载 protobuf
方法一:===> git clone https://github.com/protocolbuffers/protobuf.git 方法二:===> 或者将准备好的压缩包进行拖入 解压到$GOPATH/src/github.com/protocolbuffers/下面 Unzip protobuf.zip
-
安装(ubuntu)
(1)安装依赖工具(联网) $ sudo apt-get install autoconf automake libtool curl make g++ unzip libffi-dev -y (2)进入protobuf文件 cd protobuf/ (3)进行安装检测 并生成自动安装脚本 ./autogen.sh ./configure (4)进行编译C代码 make (5)进行安装 sudo make install (6)刷新linux共享库关系 sudo ldconfig
-
测试protobuf编译工具
protoc -h
如果正常输出 相关指令 没有报任何error,为安装成功
-
安装protobuf的go语言插件
由于protobuf并没直接支持go语言需要我们手动安装相关插件
(1)下载 方法一:===> go get -v -u github.com/golang/protobuf/proto 方法二:===>或者将 github.com-golang-protobuf.zip拖入 进行解压到 $GOPATH/src/github.com/golang (2)进入到文件夹内进行编译 $ cd $GOPATH/src/github.com/golang/protobuf/protoc-gen-go $ go build (3)将生成的 protoc-gen-go可执行文件,放在/bin目录下 $ sudo cp protoc-gen-go /bin/ (4)尝试补齐protoc-gen-go 如果可以补齐代表成功,如果执行不报错 代表工具成功
protobuf 简单语法
参考文档(需翻墙):https://developers.google.com/protocol-buffers/docs/proto3
首先让我们看一个非常简单的例子。
syntax = "proto3"; //指定版本信息,不指定会报错
package pb; //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
}
enum test{
int32 age = 0;
}
- protobuf消息的定义(或者称为描述)通常都写在一个以 .proto 结尾的文件中。
- 该文件的第一行指定正在使用
proto3
语法:如果不这样做,协议缓冲区编译器将假定正在使用proto2。这也必须是文件的第一个非空的非注释行。- 第二行package指明当前是pb包(生成go文件之后和Go的包名保持一致)
- 最后message关键字定义一个Person消息体,类似于go语言中的结构体,是包含一系列类型数据的集合。许多标准的简单数据类型都可以作为字段类型,包括
bool
,int32
,float
,double
,和string
。也可以使用其他message类型作为字段类型。- 在message中有一个字符串类型的value成员,该成员编码时用1代替名字。我们知道,在json中是通过成员的名字来绑定对应的数据,但是Protobuf编码却是通过成员的唯一编号来绑定对应的数据,因此Protobuf编码后数据的体积会比较小,能够快速传输,缺点是不利于阅读。
**message的格式说明**
消息由至少一个字段组合而成,类似于Go语言中的结构体,每个字段都有一定的格式:
```message格式说明
//注释格式 注释尽量也写在内容上方
(字段修饰符)数据类型 字段名称 = 唯一的编号标签值;
- 唯一的编号标签:代表每个字段的一个唯一的编号标签,在同一个消息里不可以重复。这些编号标签用与在消息二进制格式中标识你的字段,并且消息一旦定义就不能更改。需要说明的是标签在1到15范围的采用一个字节进行编码,所以通常将标签1到15用于频繁发生的消息字段。编号标签大小的范围是1到2的29次。19000-19999是官方预留的值,不能使用。
- 注释格式:向.proto文件添加注释,可以使用C/C++/java/Go风格的双斜杠(//) 语法格式或者
/*.....*/
message常见的数据类型与go中类型对比
.proto类型 | Go类型 | 介绍 |
---|---|---|
double | float64 | 64位浮点数 |
float | float32 | 32位浮点数 |
int32 | int32 | 使用可变长度编码。编码负数效率低下——如果你的字段可能有负值,请改用sint32。 |
int64 | int64 | 使用可变长度编码。编码负数效率低下——如果你的字段可能有负值,请改用sint64。 |
uint32 | uint32 | 使用可变长度编码。 |
uint64 | uint64 | 使用可变长度编码。 |
sint32 | int32 | 使用可变长度编码。符号整型值。这些比常规int32s编码负数更有效。 |
sint64 | int64 | 使用可变长度编码。符号整型值。这些比常规int64s编码负数更有效。 |
fixed32 | uint32 | 总是四字节。如果值通常大于228,则比uint 32更有效 |
fixed64 | uint64 | 总是八字节。如果值通常大于256,则比uint64更有效 |
sfixed32 | int32 | 总是四字节。 |
sfixed64 | int64 | 总是八字节。 |
bool | bool | 布尔类型 |
string | string | 字符串必须始终包含UTF - 8编码或7位ASCII文本 |
bytes | []byte | 可以包含任意字节序列 |
protobuf高级用法
protobuf除了上面的简单类型还有一些复杂的用法,如下:
message嵌套
messsage除了能放简单数据类型外,还能存放另外的message类型,如下:
syntax = "proto3"; //指定版本信息,不指定会报错
package pb; //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
//定义一个message
message PhoneNumber {
string number = 1;
int64 type = 2;
}
PhoneNumber phone = 3;
}
repeated关键字
repeadted关键字类似与go中的切片,编译之后对应的也是go的切片,用法如下:
syntax = "proto3"; //指定版本信息,不指定会报错
package pb; //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
//定义一个message
message PhoneNumber {
string number = 1;
int64 type = 2;
}
repeated PhoneNumber phone = 3;
}
默认值
解析数据时,如果编码的消息不包含特定的单数元素,则解析对象对象中的相应字段将设置为该字段的默认值。不同类型的默认值不同,具体如下:
- 对于字符串,默认值为空字符串。
- 对于字节,默认值为空字节。
- 对于bools,默认值为false。
- 对于数字类型,默认值为零。
- 对于枚举,默认值是第一个定义的枚举值,该值必须为0。
- repeated字段默认值是空列表
- message字段的默认值为空对象
enum关键字
在定义消息类型时,可能会希望其中一个字段有一个预定义的值列表。比如说,电话号码字段有个类型,这个类型可以是,home,work,mobile。我们可以通过enum在消息定义中添加每个可能值的常量来非常简单的执行此操作。实例如下:
syntax = "proto3"; //指定版本信息,不指定会报错
package pb; //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
//定义一个message
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phone = 3;
}
//enum为关键字,作用为定义一种枚举类型
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
如上,enum的第一个常量映射为0,每个枚举定义必须包含一个映射到零的常量作为其第一个元素。这是因为:
- 必须有一个零值,以便我们可以使用0作为数字默认值。
- 零值必须是第一个元素,以便与proto2语义兼容,其中第一个枚举值始终是默认值。
enum还可以为不同的枚举常量指定相同的值来定义别名。如果想要使用这个功能必须讲allow_alias
选项设置为true,负责编译器将报错。示例如下:
syntax = "proto3"; //指定版本信息,不指定会报错
package pb; //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
//定义一个message
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phone = 3;
}
//enum为关键字,作用为定义一种枚举类型
enum PhoneType {
//如果不设置将报错
option allow_alias = true;
MOBILE = 0;
HOME = 1;
WORK = 2;
Personal = 2;
}
oneof关键字
如果有一个包含许多字段的消息,并且最多只能同时设置其中的一个字段,则可以使用oneof功能,示例如下:
message Person{
// 名字
string name = 1;
// 年龄
int32 age = 2 ;
//定义一个message
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phone = 3;
oneof data{
string school = 5;
int32 score = 6;
}
}
定义RPC服务
如果需要将message与RPC一起使用,则可以在.proto
文件中定义RPC服务接口,protobuf编译器将根据你选择的语言生成RPC接口代码。示例如下:
//定义RPC服务
service HelloService {
rpc Hello (Person)returns (Person);
}
大部分protobuf的语法就是这样,其他想学习的可以参考官方文档,语法写完之后,让我们编译一下,然后通过代码试用一下。
protobuf基本编译
protobuf编译是通过编译器protoc进行的,通过这个编译器,我们可以把.proto文件生成go,Java,Python,C++, Ruby, JavaNano, Objective-C,或者C# 代码,生成命令如下:
protoc --proto_path=IMPORT_PATH --go_out=DST_DIR path/to/file.proto
- –proto_path=IMPORT_PATH,IMPORT_PATH是 .proto 文件所在的路径,如果忽略则默认当前目录。如果有多个目录则可以多次调用–proto_path,它们将会顺序的被访问并执行导入。
- –go_out=DST_DIR, 指定了生成的go语言代码文件放入的文件夹
- 允许使用
protoc --go_out=./ *.proto
的方式一次性编译多个 .proto 文件- go语言编译时,protobuf 编译器会把 .proto 文件编译成 .pd.go 文件
一般在使用的时候我们都是使用下面这种简单的命令:
protoc --go_out=./ *.proto
编译当前文件夹下的所有.proto文件,并把生成的go文件放置在当前文件夹下。
实验
.proto文件
syntax = "proto3";
package pb;
option go_package = "./";
// 消息体 --- 一个package 中,不允许定义同名的消息体
message Teacher {
int32 age = 1;
string name = 2;
}
// 定义 服务
service SayName {
rpc SayHello (Teacher) returns (Teacher);
}
编译后的go文件
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1-devel
// protoc v3.17.3
// source: person.proto
package __
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// 消息体 --- 一个package 中,不允许定义同名的消息体
type Teacher struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Age int32 `protobuf:"varint,1,opt,name=age,proto3" json:"age,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
}
func (x *Teacher) Reset() {
*x = Teacher{}
if protoimpl.UnsafeEnabled {
mi := &file_person_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Teacher) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Teacher) ProtoMessage() {}
func (x *Teacher) ProtoReflect() protoreflect.Message {
mi := &file_person_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Teacher.ProtoReflect.Descriptor instead.
func (*Teacher) Descriptor() ([]byte, []int) {
return file_person_proto_rawDescGZIP(), []int{0}
}
func (x *Teacher) GetAge() int32 {
if x != nil {
return x.Age
}
return 0
}
func (x *Teacher) GetName() string {
if x != nil {
return x.Name
}
return ""
}
var File_person_proto protoreflect.FileDescriptor
var file_person_proto_rawDesc = []byte{
0x0a, 0x0c, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02,
0x70, 0x62, 0x22, 0x2f, 0x0a, 0x07, 0x54, 0x65, 0x61, 0x63, 0x68, 0x65, 0x72, 0x12, 0x10, 0x0a,
0x03, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x67, 0x65, 0x12,
0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
0x61, 0x6d, 0x65, 0x32, 0x2f, 0x0a, 0x07, 0x53, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24,
0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x0b, 0x2e, 0x70, 0x62, 0x2e,
0x54, 0x65, 0x61, 0x63, 0x68, 0x65, 0x72, 0x1a, 0x0b, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x65, 0x61,
0x63, 0x68, 0x65, 0x72, 0x42, 0x04, 0x5a, 0x02, 0x2e, 0x2f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var (
file_person_proto_rawDescOnce sync.Once
file_person_proto_rawDescData = file_person_proto_rawDesc
)
func file_person_proto_rawDescGZIP() []byte {
file_person_proto_rawDescOnce.Do(func() {
file_person_proto_rawDescData = protoimpl.X.CompressGZIP(file_person_proto_rawDescData)
})
return file_person_proto_rawDescData
}
var file_person_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_person_proto_goTypes = []interface{}{
(*Teacher)(nil), // 0: pb.Teacher
}
var file_person_proto_depIdxs = []int32{
0, // 0: pb.SayName.SayHello:input_type -> pb.Teacher
0, // 1: pb.SayName.SayHello:output_type -> pb.Teacher
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_person_proto_init() }
func file_person_proto_init() {
if File_person_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_person_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Teacher); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_person_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_person_proto_goTypes,
DependencyIndexes: file_person_proto_depIdxs,
MessageInfos: file_person_proto_msgTypes,
}.Build()
File_person_proto = out.File
file_person_proto_rawDesc = nil
file_person_proto_goTypes = nil
file_person_proto_depIdxs = nil
}
参考资料:
https://developers.google.com/protocol-buffers/docs/reference/go-generated
https://www.bilibili.com/video/BV1po4y1X7hH?p=79&spm_id_from=pageDriver
https://blog.csdn.net/qq_44033530/article/details/115418377
以上是关于protobuf学习笔记的主要内容,如果未能解决你的问题,请参考以下文章
《Go-micro微服务框架入门教程》学习笔记 | Protobuf
游戏开发笔记:protobuf避免重复代码newBuilder
[原创]java WEB学习笔记61:Struts2学习之路--通用标签 property,uri,param,set,push,if-else,itertor,sort,date,a标签等(代码片段