Protobuf通信协议

Author Avatar
Yangzzz 7月 12, 2020
1.8k字 | 7分 |
  • 在其它设备中阅读本文章

RPC的调用过程

一个正常的RPC过程可以分为一下几个步骤:

  • client调用client stub,这是一次本地过程调用。
  • client stub将参数打包成一个消息,然后发送这个消息。打包过程也叫做marshalling。
  • client所在的系统将消息发送给server。
  • server的的系统将收到的包传给server stub。
  • server stub解包得到参数。 解包也被称作 unmarshalling。
  • server stub调用服务过程。返回结果按照相反的步骤传给client。

在上述的步骤实现远程接口调用时,所需要执行的函数是存在于远程机器中,即函数是在另外一个进程中执行的。因此,就带来了几个新问题:

  • 1、Call ID映射。远端进程中间可以包含定义的多个函数,本地客户端该如何告知远端进程程序调用特定的某个函数呢?因此,在RPC调用过程中,所有的函数都需要有一个自己的ID。开发者在客户端(调用端)和服务端(被调用端)分别维护一个{函数<–>Call ID}的对应表。两者的表不一定完全相同,但是相同的函数对应的Call ID必须相同。当客户端需要进行远程调用时,调用者通过映射表查询想要调用的函数的名称,找到对应的Call ID,然后传递给服务端,服务端也通过查表,来确定客户端所需要调用的函数,然后执行相应函数的代码。
  • 2、序列化与反序列化。客户端如何把参数传递给远程调用的函数呢?在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言(比如服务端用C++,客户端用Java或者Python)。这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。这个过程叫序列化和反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。
  • 3、网络传输。远程调用往往用在网络上,客户端和服务端是通过网络连接的。所有的数据都需要通过网络传输,因此就需要有一个网络传输层。网络传输层需要把Call ID和序列化后的参数字节流传递给服务端,然后在把序列化后的调用结果传回给客户端,完成这种数据传递功能的被成为传输层。大部分的网络传输成都使用TCP协议,属于长连接。

有对传递的数据进行序列化和反序列化的操作,这就是我们这里要说的:Protobuf。

简介

Google Protocol Buffer( 简称 Protobuf)是Google公司内部的混合语言数据标准,他们主要用于RPC系统和持续数据存储系统。

Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或RPC数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。简单来说,Protobuf的功能类似于XML,即负责把某种数据结构的信息,以某种格式保存起来。主要用于数据存储、传输协议等使用场景。

安装

安装protobuf编译器

可以在如下地址:https://github.com/protocolbuffers/protobuf/releases选择适合自己系统的Proto编译器程序进行下载并解压。然后配置环境变量:将protocke执行文件所在目录添加到当前系统的环境变量中。windows系统下可以直接在Path目录中进行添加。

安装go依赖

go get github.com/golang/protobuf/protoc-gen-go

Protobuf 协议语法

message:

Protobuf中定义一个数据结构需要用到关键字message,这一点和Java的class,Go语言中的struct类似。

标识号:

在消息的定义中,每个字段等号后面都有唯一的标识号,用于在反序列化过程中识别各个字段的,一旦开始使用就不能改变。标识号从整数1开始,依次递增,每次增加1,标识号的范围为1~2^29 – 1,其中[19000-19999]为Protobuf协议预留字段,开发者不建议使用该范围的标识号;一旦使用,在编译时Protoc编译器会报出警告。

字段规则:

字段规则有三种:

  • 1.required:该规则规定,消息体中该字段的值是必须要设置的。
  • 2.optional:消息体中该规则的字段的值可以存在,也可以为空,optional的字段可以根据defalut设置默认值。
  • 3.repeated:消息体中该规则字段可以存在多个(包括0个),该规则对应java的数组或者go语言的slice。

数据类型:

常见的数据类型与protoc协议中的数据类型映射如下:

枚举类型:

proto协议支持使用枚举类型

enum Sex{
    male=1;
    female=2;
}

字段默认值:

message Address {
    required sint32 id = 1 [default = 1];
    required string name = 2 [default = '北京'];
    optional string pinyin = 3 [default = 'beijing'];
    required string address = 4;
    required bool flag = 5 [default = true];
}

导入

如果需要引用的message是写在别的.proto文件中,可以通过import “xxx.proto”来进行引入

嵌套

syntax = "proto3";
package example;
message Person {
    required string Name = 1;
    required int32 Age = 2;
    required string From = 3;
    optional Address Addr = 4;
    message Address {
        required sint32 id = 1;
        required string name = 2;
        optional string pinyin = 3;
        required string address = 4;
    }
}

生成文件

这里看你,需要使用什么语言的,如果你需要再c和py之间传输数据,你就需要生成对应的c和py文件,就行。这里我因为使用的golang。我就生成go的

protoc --go_out=. *.proto

这里推荐下几个插件(golang)

  • protoc-gen-gogo:和protoc-gen-go生成的文件差不多,性能也几乎一样(稍微快一点点)

  • protoc-gen-gofast:生成的文件更复杂,性能也更高(快5-7倍)

//gogo
go get github.com/gogo/protobuf/protoc-gen-gogo

//gofast
go get github.com/gogo/protobuf/protoc-gen-gofast

安装gogoprotobuf库文件

go get github.com/gogo/protobuf/proto
go get github.com/gogo/protobuf/gogoproto  //这个不装也没关系

生成文件

//gogo
protoc --gogo_out=. *.proto

//gofast
protoc --gofast_out=. *.proto

性能测试

//goprotobuf
"编码":447ns/op
"解码":422ns/op

//gogoprotobuf-go
"编码":433ns/op
"解码":427ns/op

//gogoprotobuf-fast
"编码":112ns/op
"解码":112ns/op

使用

发送消息

把消息进行编码


book := &pb.AddressBook{}
// ...

out, err := proto.Marshal(book)
if err != nil {
        log.Fatalln("Failed to encode address book:", err)
}
if err := ioutil.WriteFile(fname, out, 0644); err != nil {
        log.Fatalln("Failed to write address book:", err)
}

接收消息

把消息进行解码

in, err := ioutil.ReadFile(fname)
if err != nil {
        log.Fatalln("Error reading file:", err)
}
book := &pb.AddressBook{}
if err := proto.Unmarshal(in, book); err != nil {
        log.Fatalln("Failed to parse address book:", err)
}

参考连接:https://developers.google.com/protocol-buffers/docs/gotutorial

本文使用CC BY-NC-SA 3.0 中国大陆协议许可
本文链接:https://myxy99.cn/posts/golang/ac0a6a86.html