Go语言,Protobuf 入门详解!

简介: Protobuf 是 Protocol Buffers 的简称,是一种与语言、平台无关,可扩展的序列化结构化数据的数据描述语言,Protobuf作为接口规范的描述语言,可以作为设计安全的跨语言PRC接口的基础工具。

Protobuf 是 Protocol Buffers 的简称,是一种与语言、平台无关,可扩展的序列化结构化数据的数据描述语言,Protobuf作为接口规范的描述语言,可以作为设计安全的跨语言PRC接口的基础工具。

基本语法

image.png

hello.proto 文件

syntax = "proto3";
package main;
message String {
    string value = 1;
}
复制代码
  1. 第一行声明使用 proto3 语法。否则,默认使用 proto2 语法,目前主流推荐使用 v3 版本。此声明必须是文件的非空、非注释的第一行。
  2. package 指令指明当前是 main 包,用户也可以针对不同的语言定制对应的包路径和名称。
  3. message 关键字定义一个 String 类型消息体,在最终生成的Go语言代码中对应一个 String 结构体。每一个消息体的字段包含三个属性:类型、字段名称、字段编号。在消息体的定义上,除类型以外均不可重复。此处 String 类型中只有一个字符串类型的 value 成员,该成员编码时用1编号代替名字。
  4. 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";
message Stock {
    reserved 3, 4;
    // ...
}
复制代码

还可以将 reserved 关键字用作将来可能添加的字段的占位符。 可以使用 to 关键字将连续字段号占位。

syntax "proto3";
message Info {
    reserved 2, 9 to 11, 15;
    // ...
}
复制代码

生成相应的Go代码

Protobuf 核心的工具集是 C++ 语言开发的,官方的 protoc 编译器中并不支持Go语言。要想基于上面 的 hello.proto 文件生成相应的Go代码,需要安装相应的插件。

  • 安装官方的 protoc 工具,可以从 github.com/google/prot… 下载。
  • 安装针对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 所生成出来的数据类型并非与原始的类型完全一致,下面是一些常见的类型映射:


image.png

生成的 hello.pb.go 文件

pb.go 文件是对 proto 文件所生成的对应的 Go 代码,在实际应用中将会引用到此文件。

// Code generated by protoc-gen-go. DO NOT EDIT.
// source: hello.proto
package main
import (
  fmt "fmt"
  proto "github.com/golang/protobuf/proto"
  math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type String struct {
  Value                *String  `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
  XXX_NoUnkeyedLiteral struct{} `json:"-"`
  XXX_unrecognized     []byte   `json:"-"`
  XXX_sizecache        int32    `json:"-"`
}
func (m *String) Reset()         { *m = String{} }
func (m *String) String() string { return proto.CompactTextString(m) }
func (*String) ProtoMessage()    {}
func (*String) Descriptor() ([]byte, []int) {
  return fileDescriptor_61ef911816e0a8ce, []int{0}
}
func (m *String) XXX_Unmarshal(b []byte) error {
  return xxx_messageInfo_String.Unmarshal(m, b)
}
func (m *String) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
  return xxx_messageInfo_String.Marshal(b, m, deterministic)
}
func (m *String) XXX_Merge(src proto.Message) {
  xxx_messageInfo_String.Merge(m, src)
}
func (m *String) XXX_Size() int {
  return xxx_messageInfo_String.Size(m)
}
func (m *String) XXX_DiscardUnknown() {
  xxx_messageInfo_String.DiscardUnknown(m)
}
var xxx_messageInfo_String proto.InternalMessageInfo
func (m *String) GetValue() *String {
  if m != nil {
    return m.Value
  }
  return nil
}
func init() {
  proto.RegisterType((*String)(nil), "main.String")
}
func init() { proto.RegisterFile("hello.proto", fileDescriptor_61ef911816e0a8ce) }
var fileDescriptor_61ef911816e0a8ce = []byte{
  // 84 bytes of a gzipped FileDescriptorProto
  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 服务

package main
import (
  "log"
  "net"
  "net/rpc"
  "rpc/protoc"
)
// HelloService is rpc server obj
type HelloService struct{}
//Hello方法的输入参数和输出的参数均改用 Protobuf 定义的 String 类型表示。
//因为新的输入参数为结构体类型,因此改用指针类型作为输入参数,函数的内部代码同时也做了相应的调整。
func (p *HelloService) Hello(request *protoc.String, reply *protoc.String) error {
  reply.Value = "hello:" + request.GetValue()
  return nil
}
func main() {
  rpc.RegisterName("HelloService", new(HelloService))
  listener, err := net.Listen("tcp", ":1234")
  if err != nil {
    log.Fatal("ListenTCP error:", err)
  }
  conn, err := listener.Accept()
  if err != nil {
    log.Fatal("Accept error", err)
  }
  rpc.ServeConn(conn)
}
复制代码

下面是客户端请求HelloService服务的代码 client.go:

package main
import (
  "fmt"
  "log"
  "net/rpc"
  "rpc/protoc"
)
func main() {
  client, err := rpc.Dial("tcp", "localhost:1234")
  if err != nil {
    log.Fatal("dialing err:", err)
  }
  var reply = &protoc.String{}
  var param = &protoc.String{
    Value: "hello wekenw",
  }
  err = client.Call("HelloService.Hello", &param, &reply)
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println(reply)
}
复制代码

开启服务器端,开启客户端。客户端的执行结果如下:

$ go run client.go
value:"hello:hello wekenw"


相关文章
|
16天前
|
存储 监控 算法
员工上网行为监控中的Go语言算法:布隆过滤器的应用
在信息化高速发展的时代,企业上网行为监管至关重要。布隆过滤器作为一种高效、节省空间的概率性数据结构,适用于大规模URL查询与匹配,是实现精准上网行为管理的理想选择。本文探讨了布隆过滤器的原理及其优缺点,并展示了如何使用Go语言实现该算法,以提升企业网络管理效率和安全性。尽管存在误报等局限性,但合理配置下,布隆过滤器为企业提供了经济有效的解决方案。
60 8
员工上网行为监控中的Go语言算法:布隆过滤器的应用
|
1月前
|
存储 Go 索引
go语言中数组和切片
go语言中数组和切片
42 7
|
1月前
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
1月前
|
程序员 Go
go语言中结构体(Struct)
go语言中结构体(Struct)
105 71
|
1月前
|
存储 Go 索引
go语言中的数组(Array)
go语言中的数组(Array)
109 67
|
1天前
|
监控 安全 算法
深度剖析核心科技:Go 语言赋能局域网管理监控软件进阶之旅
在局域网管理监控中,跳表作为一种高效的数据结构,能显著提升流量索引和查询效率。基于Go语言的跳表实现,通过随机化索引层生成、插入和搜索功能,在高并发场景下展现卓越性能。跳表将查询时间复杂度优化至O(log n),助力实时监控异常流量,保障网络安全与稳定。示例代码展示了其在实际应用中的精妙之处。
24 9
|
11天前
|
算法 安全 Go
Go 语言中实现 RSA 加解密、签名验证算法
随着互联网的发展,安全需求日益增长。非对称加密算法RSA成为密码学中的重要代表。本文介绍如何使用Go语言和[forgoer/openssl](https://github.com/forgoer/openssl)库简化RSA加解密操作,包括秘钥生成、加解密及签名验证。该库还支持AES、DES等常用算法,安装简便,代码示例清晰易懂。
46 12
|
1月前
|
存储 Go
go语言中映射
go语言中映射
42 11
|
14天前
|
监控 算法 安全
解锁企业计算机监控的关键:基于 Go 语言的精准洞察算法
企业计算机监控在数字化浪潮下至关重要,旨在保障信息资产安全与高效运营。利用Go语言的并发编程和系统交互能力,通过进程监控、网络行为分析及应用程序使用记录等手段,实时掌握计算机运行状态。具体实现包括获取进程信息、解析网络数据包、记录应用使用时长等,确保企业信息安全合规,提升工作效率。本文转载自:[VIPShare](https://www.vipshare.com)。
22 0
|
28天前
|
Go 数据安全/隐私保护 UED
优化Go语言中的网络连接:设置代理超时参数
优化Go语言中的网络连接:设置代理超时参数