Go语言,Protobuf 极速入门!
发布于 2021-11-18 17:01 ,所属分类:软件编程学习资料
Protobuf 是 Protocol Buffers 的简称,是一种与语言、平台无关,可扩展的序列化结构化数据的数据描述语言,Protobuf作为接口规范的描述语言,可以作为设计安全的跨语言PRC接口的基础工具。
基本语法
hello.proto 文件
syntax="proto3";
packagemain;
messageString{
stringvalue=1;
}
第一行声明使用 proto3 语法。否则,默认使用 proto2 语法,目前主流推荐使用 v3 版本。此声明必须是文件的非空、非注释的第一行。 package 指令指明当前是 main 包,用户也可以针对不同的语言定制对应的包路径和名称。 message 关键字定义一个 String 类型消息体,在最终生成的Go语言代码中对应一个 String 结构体。每一个消息体的字段包含三个属性:类型、字段名称、字段编号。在消息体的定义上,除类型以外均不可重复。此处 String 类型中只有一个字符串类型的 value 成员,该成员编码时用1编号代替名字。 Protobuf 中最基本的数据单元是 message,类似 Go 语言中的结构体。在 message 中可以嵌套 message 或其它的基础数据类型的成员。
关于标识号
消息体中字段定义了唯一的数字值。这些数字是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。
最小的标识号可以从1开始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999]的标识号, Protobuf 协议实现中对这些进行了预留。如果非要在 .proto 文件中使用这些预留标识号,编译时就会报警。类似地,你不能使用之前保留的任何标识符。
添加注释
.proto 文件添加注释,可以使用C/C++风格的 // 和 /* … */ 语法格式
保留字段
如果从前面定义的消息中删除了 和 字段,应保留其字段编号,使用关键字 reserved
:
syntax"proto3";
messageStock{
reserved3,4;
//...
}
还可以将 reserved 关键字用作将来可能添加的字段的占位符。可以使用 to
关键字将连续字段号占位。
syntax"proto3";
messageInfo{
reserved2,9to11,15;
//...
}
生成相应的Go代码
Protobuf 核心的工具集是 C++ 语言开发的,官方的 protoc 编译器中并不支持Go语言。要想基于上面 的 hello.proto 文件生成相应的Go代码,需要安装相应的插件。
安装官方的 protoc 工具,可以从 https://github.com/google/protobuf/releases 下载。 安装针对Go语言的代码生成插件,通过 go get github.com/golang/protobuf/protoc-gen-go 命令安装。
通过以下命令生成相应的Go代码:
$ protoc --go_out=. hello.proto
go_out 参数告知 protoc 编译器去加载对应的 protoc-gen-go 工具,生成的代码放到当前目录。最后是一系列要处理的protobuf文件的列表。
plugins=plugin1+plugin2:指定要加载的子插件列表,我们定义的 proto 文件是涉及了 RPC 服务的,而默认是不会生成 RPC 代码的,因此需要在 go_out 中给出 plugins 参数传递给 protoc-gen-go,告诉编译器,请支持 RPC(这里指定了内置的 grpc 插件)。
基本数据类型
protobuf 所生成出来的数据类型并非与原始的类型完全一致,下面是一些常见的类型映射:
生成的 hello.pb.go 文件
pb.go 文件是对 proto 文件所生成的对应的 Go 代码,在实际应用中将会引用到此文件。
//Codegeneratedbyprotoc-gen-go.DONOTEDIT.
//source:hello.proto
packagemain
import(
fmt"fmt"
proto"github.com/golang/protobuf/proto"
math"math"
)
//Referenceimportstosuppresserrorsiftheyarenototherwiseused.
var_=proto.Marshal
var_=fmt.Errorf
var_=math.Inf
//Thisisacompile-timeassertiontoensurethatthisgeneratedfile
//iscompatiblewiththeprotopackageitisbeingcompiledagainst.
//Acompilationerroratthislinelikelymeansyourcopyofthe
//protopackageneedstobeupdated.
const_=proto.ProtoPackageIsVersion3//pleaseupgradetheprotopackage
typeStringstruct{
Value*String`protobuf:"bytes,1,opt,name=value,proto3"json:"value,omitempty"`
XXX_NoUnkeyedLiteralstruct{}`json:"-"`
XXX_unrecognized[]byte`json:"-"`
XXX_sizecacheint32`json:"-"`
}
func(m*String)Reset(){*m=String{}}
func(m*String)String()string{returnproto.CompactTextString(m)}
func(*String)ProtoMessage(){}
func(*String)Descriptor()([]byte,[]int){
returnfileDescriptor_61ef911816e0a8ce,[]int{0}
}
func(m*String)XXX_Unmarshal(b[]byte)error{
returnxxx_messageInfo_String.Unmarshal(m,b)
}
func(m*String)XXX_Marshal(b[]byte,deterministicbool)([]byte,error){
returnxxx_messageInfo_String.Marshal(b,m,deterministic)
}
func(m*String)XXX_Merge(srcproto.Message){
xxx_messageInfo_String.Merge(m,src)
}
func(m*String)XXX_Size()int{
returnxxx_messageInfo_String.Size(m)
}
func(m*String)XXX_DiscardUnknown(){
xxx_messageInfo_String.DiscardUnknown(m)
}
varxxx_messageInfo_Stringproto.InternalMessageInfo
func(m*String)GetValue()*String{
ifm!=nil{
returnm.Value
}
returnnil
}
funcinit(){
proto.RegisterType((*String)(nil),"main.String")
}
funcinit(){proto.RegisterFile("hello.proto",fileDescriptor_61ef911816e0a8ce)}
varfileDescriptor_61ef911816e0a8ce=[]byte{
//84bytesofagzippedFileDescriptorProto
0x1f,0x8b,0x08,0x00,0x00,0x00,0x00,0x00,0x02,0xff,0xe2,0xe2,0xce,0x48,0xcd,0xc9,
0xc9,0xd7,0x2b,0x28,0xca,0x2f,0xc9,0x17,0x62,0xc9,0x4d,0xcc,0xcc,0x53,0xd2,0xe1,
0x62,0x0b,0x2e,0x29,0xca,0xcc,0x4b,0x17,0x52,0xe2,0x62,0x2d,0x4b,0xcc,0x29,0x4d,
0x95,0x60,0x54,0x60,0xd4,0xe0,0x36,0xe2,0xd1,0x03,0xc9,0xeb,0x41,0x24,0x83,0x20,
0x52,0x49,0x6c,0x60,0xad,0xc6,0x80,0x00,0x00,0x00,0xff,0xff,0x76,0x0c,0x0e,0x54,
0x49,0x00,0x00,0x00,
}
String 类型自动生成了一组方法,其中 ProtoMessage 方法表示这是一个实现了 proto.Message 接口的方法。此外 Protobuf 还为每个成员生成了一个Get方法,能够提供便捷的取值方式,并且处理了一些空指针取值的情况,还能够通过 Reset 方法来重置该参数。
.pb.go 文件的初始化方法,注意 fileDescriptor 的相关语句。fileDescriptor_61ef911816e0a8ce 表示是一个经过编译后的 proto 文件,是对 proto 文件的整体描述,其包含了 proto 文件名、引用(import)内容、包(package)名、选项设置、所有定义的消息体(message)、所有定义的枚举(enum)、所有定义的服务( service)、所有定义的方法(rpc method)等等内容。
每一个 Message Type 中都包含了 Descriptor 方法,Descriptor 代指对一个消息体(message)定义的描述,而这一个方法则会在 fileDescriptor 中寻找属于自己 Message Field 所在的位置再进行返回。
Protobuf 和 RPC组合
基于 String 类型,重新实现 HelloService 服务
packagemain
import(
"log"
"net"
"net/rpc"
"rpc/protoc"
)
//HelloServiceisrpcserverobj
typeHelloServicestruct{}
//Hello方法的输入参数和输出的参数均改用 Protobuf 定义的 String 类型表示。
//因为新的输入参数为结构体类型,因此改用指针类型作为输入参数,函数的内部代码同时也做了相应的调整。
func(p*HelloService)Hello(request*protoc.String,reply*protoc.String)error{
reply.Value="hello:"+request.GetValue()
returnnil
}
funcmain(){
rpc.RegisterName("HelloService",new(HelloService))
listener,err:=net.Listen("tcp",":1234")
iferr!=nil{
log.Fatal("ListenTCPerror:",err)
}
conn,err:=listener.Accept()
iferr!=nil{
log.Fatal("Accepterror",err)
}
rpc.ServeConn(conn)
}
下面是客户端请求HelloService服务的代码 client.go:
packagemain
import(
"fmt"
"log"
"net/rpc"
"rpc/protoc"
)
funcmain(){
client,err:=rpc.Dial("tcp","localhost:1234")
iferr!=nil{
log.Fatal("dialingerr:",err)
}
varreply=&protoc.String{}
varparam=&protoc.String{
Value:"hellowekenw",
}
err=client.Call("HelloService.Hello",¶m,&reply)
iferr!=nil{
log.Fatal(err)
}
fmt.Println(reply)
}
开启服务器端,开启客户端。客户端的执行结果如下:
$gorunclient.go
value:"hello:hellowekenw"
图片及部分相关技术知识点来源于网络搜索,侵权删!
参考资料:
https://blog.csdn.net/qq_22660775/article/details/89163881
https://docs.microsoft.com/zh-cn/dotnet/architecture/grpc-for-wcf-developers/protobuf-reserved
https://golang2.eddycjy.com/posts/ch3/01-simple-grpc-protobuf/
《Go语言高级编程》
NEW////ARRIVAL
gongzhong号
gophpython
我的
wucs_dd
相关资源