protobuf学习笔记

Posted Demonwuwen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了protobuf学习笔记相关的知识,希望对你有一定的参考价值。

简介

Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。

  1. protobuf是类似与json一样的数据描述语言(数据格式)
  2. 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

如果要生产grpc文件,还要使用以下命令进行安装

go install google.golang.org/grpc/cmd/protoc-gen-go-grpc

当你的go/bin目录下出现如下两个命令,说明已经安装成功了

再次执行命令编译如果出现错误


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

  1. 下载 protobuf

    方法一:===> git clone https://github.com/protocolbuffers/protobuf.git
    
    方法二:===> 或者将准备好的压缩包进行拖入
    	解压到$GOPATH/src/github.com/protocolbuffers/下面
    	Unzip protobuf.zip
    
  2. 安装(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
    
  3. 测试protobuf编译工具

    protoc -h
    

    如果正常输出 相关指令 没有报任何error,为安装成功

  4. 安装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语言中的结构体,是包含一系列类型数据的集合。许多标准的简单数据类型都可以作为字段类型,包括boolint32floatdouble,和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类型介绍
doublefloat6464位浮点数
floatfloat3232位浮点数
int32int32使用可变长度编码。编码负数效率低下——如果你的字段可能有负值,请改用sint32。
int64int64使用可变长度编码。编码负数效率低下——如果你的字段可能有负值,请改用sint64。
uint32uint32使用可变长度编码。
uint64uint64使用可变长度编码。
sint32int32使用可变长度编码。符号整型值。这些比常规int32s编码负数更有效。
sint64int64使用可变长度编码。符号整型值。这些比常规int64s编码负数更有效。
fixed32uint32总是四字节。如果值通常大于228,则比uint 32更有效
fixed64uint64总是八字节。如果值通常大于256,则比uint64更有效
sfixed32int32总是四字节。
sfixed64int64总是八字节。
boolbool布尔类型
stringstring字符串必须始终包含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
  1. –proto_path=IMPORT_PATH,IMPORT_PATH是 .proto 文件所在的路径,如果忽略则默认当前目录。如果有多个目录则可以多次调用–proto_path,它们将会顺序的被访问并执行导入。
  2. –go_out=DST_DIR, 指定了生成的go语言代码文件放入的文件夹
  3. 允许使用 protoc --go_out=./ *.proto 的方式一次性编译多个 .proto 文件
  4. go语言编译时,protobuf 编译器会把 .proto 文件编译成 .pd.go 文件

一般在使用的时候我们都是使用下面这种简单的命令:

protoc --go_out=./ *.proto

编译当前文件夹下的所有.proto文件,并把生成的go文件放置在当前文件夹下。

注:如果在proto里面添加了RPC服务,上诉命令无法生成rpc添加的服务,这时需要使用下面的命令:

protoc --go_out=plugins=grpc:. *.proto

上诉命令我自己在实验的时候是失败了的,所以找方法,后面使用如下命令可以编译为grpc.go文件

protoc --go-grpc_out=. *.proto

message.proto为你要编译的文件

实验
.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(), []int0


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学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

protobuf学习笔记

《Go-micro微服务框架入门教程》学习笔记 | Protobuf

box-shadow学习笔记

自学it18大数据笔记-第二阶段ProtoBuf-day1——会持续更新……

grpc学习笔记

hfss 增益为啥会出现负值