protobuf

protobuf

简介

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

Protobuf刚开源时的定位类似于XML、JSON等数据描述语言,通过附带工具生成代码并实现将结构化数据序列化的功能。这里我们更关注的是Protobuf作为接口规范的描述语言,可以作为设计安全的跨语言RPC接口的基础

protobuf是类似与json一样的数据描述语言(数据格式)

protobuf非常适合于RPC数据交换格式


优点

1:序列化后体积相比Json和XML很小,适合网络传输

2:支持跨平台多语言

3:消息格式升级和兼容性还不错

4:序列化反序列化速度很快,快于Json的处理速度

缺点

1:应用不够广(相比xml和json)

2:二进制格式导致可读性差

3:缺乏自描述

protobuf 简单语法

参考文档:https://developers.google.com/protocol-buffers/docs/proto3

syntax = "proto3";
//指定生成在哪个文件夹下,;后为包名
option go_package ="/;pb";
//现在不用package
//package pb;
// 算术运算请求结构
message ArithRequest {
    int32 a = 1;
    int32 b = 2;
}

// 算术运算响应结构
message ArithResponse {
    int32 pro = 1;  // 乘积
    int32 quo = 2;  // 商
    int32 rem = 3;  // 余数
}


// rpc方法
service ArithService {
    rpc multiply (ArithRequest) returns (ArithResponse);    // 乘法运算方法
    rpc divide (ArithRequest) returns (ArithResponse);      // 除法运算方法
}

message的格式说明

消息由至少一个字段组合而成,类似于Go语言中的结构体,每个字段都有一定的格式:

  • 唯一的编号标签:代表每个字段的一个唯一的编号标签,在同一个消息里不可以重复。这些编号标签用与在消息二进制格式中标识你的字段,并且消息一旦定义就不能更改。需要说明的是标签在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";                         //指定版本信息,不指定会报错

//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";                         //指定版本信息,不指定会报错
//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";                         //指定版本信息,不指定会报错
//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";                         //指定版本信息,不指定会报错
//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编译是通过编译器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服务,再次进行编译,发现生成的go文件没有发生变化。这是因为世界上的RPC实现有很多种,protoc编译器并不知道该如何为HelloService服务生成代码。不过在protoc-gen-go内部已经集成了一个叫grpc的插件,可以针对grpc生成代码:

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

查看生成的文件arith-pb.go:

包含:client接口待实现

image-20211223222405885

service 端:接口待实现

image-20211223222447294

其实就是之前我们写的客户端和服务器端的封装,我们接下来只要继承服务端接口,实现下函数即可,具体请看GRPC的使用。


   转载规则


《protobuf》 朱林刚 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
微服务简介 微服务简介
微服务简介单体式架构服务特点: 复杂性逐渐变高 中软国际 boss计费系统 十几年了 移动联通缴费平台 几个亿 自己封装函数 代码冗余度特别大 比如有几十万行代码的大项目,代码越多复杂性越高,越难解决遇到的问
2021-12-24
下一篇 
RPC简介 RPC简介
RPC简介Remote Procedure Call Protocol —— 远程过程调用协议 RPC(Remote Procedure Call Protocol),是远程过程调用的缩写,通俗的说就是调用远处的一个函数。 理解RPC
2021-12-24
  目录