当前位置:   article > 正文

ProtoBuf 入门教程_protobuf入门

protobuf入门

ProtoBuf 入门教程

一、 简介

  1. 在网络通信和通用数据交换等应用场景中经常使用的技术是 JSON 或 XML,本教程介绍另外一个数据交换的协议的工具ProtoBuf.
  2. protocol buffers (ProtoBuf)是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。
  3. Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。
  4. json\xml都是基于文本格式,protobuf是二进制格式。
  5. 你可以通过 ProtoBuf 定义数据结构,然后通过 ProtoBuf 工具生成各种语言版本的数据结构类库,用于操作 ProtoBuf 协议数据
  6. 本教程介绍的是最新的protobuf proto3版本的语法。

二、ProtoBuf的例子

  1. 创建 .proto 文件,定义数据结构;使用 ProtoBuf ,首先需要通过 ProtoBuf 语法定义数据结构(消息),这些定义好的数据结构保存在.proto为后缀的文件中(文件名: response.proto)
  2. // 指定protobuf的版本,proto3是最新的语法版本
    syntax = "proto3";
    
    // 定义数据结构,message 你可以想象成java的class,c语言中的struct
    message Response {
      string data = 1;   // 定义一个string类型的字段,字段名字为data, 序号为1
      int32 status = 2;   // 定义一个int32类型的字段,字段名字为status, 序号为2
    }
    //说明:proto文件中,字段后面的序号,不能重复,定义了就不能修改,可以理解成字段的唯一ID。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

三、 安装ProtoBuf编译器

  1. protobuf的github发布地址: https://github.com/protocolbuffers/protobuf/releases
  2. protobuf的编译器叫protoc,在上面的网址中找到最新版本的安装包,下载安装
  3. 这里下载的是:protoc-3.9.1-win64.zip , windows 64位系统版本的编译器,下载后,解压到你想要的安装目录即可
  4. 安装完成后,将 [protoc安装目录]/bin 路径添加到PATH环境变量中

四、将.proto文件,编译成指定语言类库

  1. protoc编译器支持将proto文件编译成多种语言版本的代码,我们这里以java为例。

    切换到proto文件所在的目录, 执行下面命令

  2. protoc --java_out=. response.proto
    
    • 1

五、使用ProtoBuf对数据进行序列化和反序列化

  1. 因为上面的例子使用的是java, 我们先导入protobuf的基础类库。

    maven:

    <dependency>
                <groupId>com.google.protobuf</groupId>
                <artifactId>protobuf-java</artifactId>
                <version>3.9.1</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
  2. ResponseOuterClass.Response.Builder builder = ResponseOuterClass.Response.newBuilder();
    // 设置字段值
    builder.setData("hello www.tizi365.com");
    builder.setStatus(200);
    
     ResponseOuterClass.Response response = builder.build();
     // 将数据根据protobuf格式,转化为字节数组
     byte[] byteArray  = response.toByteArray();
    
    // 反序列化,二进制数据
    try {
        ResponseOuterClass.Response newResponse = ResponseOuterClass.Response.parseFrom(byteArray);
        System.out.println(newResponse.getData());
        System.out.println(newResponse.getStatus());
    } catch (Exception e) {
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

六、protobuf 定义消息

  1. 消息(message),在protobuf中指的就是我们要定义的数据结构。

  2. syntax = "proto3";
     
    message SearchRequest {
      string query = 1;
      int32 page_number = 2;
      int32 result_per_page = 3;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  3. 我们通常将protobuf消息定义保存在.proto为后缀的文件中。

  4. syntax关键词定义使用的是proto3语法版本,如果没有指定默认使用的是proto2。

  5. message关键词,标记开始定义一个消息,消息体,用于定义各种字段类型。

  6. protobuf消息定义的语法结构,跟我们平时接触的各种语言的类定义,非常相似。

  7. 定义了一个SearchRequest消息,这个消息有3个字段,query是字符串类型,page_number和result_per_page是int32类型。

七、字段类型

  1. 支持多种数据类型,例如:string、int32、double、float等等

  2. Protobuf定义了一套基本数据类型,下表罗列出了protobuf类型和其他语言类型的映射表。

  3. 枚举类型,当需要定义一个消息类型的时候,可能想为一个字段指定“预定义值序列”中的一个值,这时候可以通过枚举实现

    syntax = "proto3";//指定版本信息,不指定会报错
    
    enum PhoneType //枚举消息类型,使用enum关键词定义,一个电话类型的枚举类型
    {
        MOBILE = 0; //proto3版本中,首成员必须为0,成员不应有相同的值
        HOME = 1;
        WORK = 2;
    }
    
    // 定义一个电话消息
    message PhoneNumber
    {
        string number = 1; // 电话号码字段
        PhoneType type = 2; // 电话类型字段,电话类型使用PhoneType枚举类型
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  4. 数组类型,在protobuf消息中定义数组类型,是通过在字段前面增加repeated关键词实现,标记当前字段是一个数组。

    //整数数组的例子:
    message Msg {
      // 只要使用repeated标记类型定义,就表示数组类型。
      repeated int32 arrays = 1;
    }
    //字符串数组
    message Msg {
      repeated string names = 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  5. 消息嵌套,我们在各种语言开发中类的定义是可以互相嵌套的,也可以使用其他类作为自己的成员属性类型。在protobuf中同样支持消息嵌套,可以在一个消息中嵌套另外一个消息,字段类型可以是另外一个消息类型。

    //1.引用其他消息类型的用法
    
    // 定义Result消息
    message Result {
      string url = 1;
      string title = 2;
      repeated string snippets = 3; // 字符串数组类型
    }
    
    // 定义SearchResponse消息
    message SearchResponse {
      // 引用上面定义的Result消息类型,作为results字段的类型
      repeated Result results = 1; // repeated关键词标记,说明results字段是一个数组
    }
    
    //2.消息嵌套
    message SearchResponse {
      // 嵌套消息定义
      message Result {
        string url = 1;
        string title = 2;
        repeated string snippets = 3;
      }
      // 引用嵌套的消息定义
      repeated Result results = 1;
    }
    
    //3.import导入其他proto文件定义的消息
    //我们在开发一个项目的时候通常有很多消息定义,都写在一个proto文件,不方便维护,通常会将消息定义写在不同的proto文件中,在需要的时候可以通过import导入其他proto文件定义的消息。
    
    //保存文件: result.proto
    syntax = "proto3";
    // Result消息定义
    message Result {
      string url = 1;
      string title = 2;
      repeated string snippets = 3; // 字符串数组类型
    }
    
    //保存文件: search_response.proto
    syntax = "proto3";
    // 导入Result消息定义
    import "result.proto";
    
    // 定义SearchResponse消息
    message SearchResponse {
      // 使用导入的Result消息
      repeated Result results = 1; 
    }
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
  6. map类型,protocol buffers支持map类型定义。

    map<key_type, value_type> map_field = N;
    key_type可以是任何整数或字符串类型(除浮点类型和字节之外的任何标量类型)。请注意,枚举不是有效的key_type。
    value_type 可以是除另一个映射之外的任何类型。
    
    syntax = "proto3";
    message Product
    {
        string name = 1; // 商品名
        // 定义一个k/v类型,key是string类型,value也是string类型
        map<string, string> attrs = 2; // 商品属性,键值对
    }
    
    //Map 字段不能使用repeated关键字修饰。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

八、分配标识号

  1. 通过前面的例子,在消息定义中,每个字段后面都有一个唯一的数字,这个就是标识号。
  2. 这些标识号是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变,每个消息内唯一即可,不同的消息定义可以拥有相同的标识号。
  3. [1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的字段预留一些标识号。

九、保留标识号(Reserved)

  1. 如果你想保留一些标识号,留给以后用,可以使用下面语法:

  2. message Foo {
      reserved 2, 15, 9 to 11; // 保留2,15,9到11这些标识号
    }
    
    • 1
    • 2
    • 3
  3. 如果使用了这些保留的标识号,protocol buffer编译器会输出警告信息。

十、注释

  1. 往.proto文件添加注释,支持C/C++/java风格的双斜杠(//) 语法格式。

  2. // 定义SearchRequest消息
    message SearchRequest {
      string query = 1;
      int32 page_number = 2;  // 页码
      int32 result_per_page = 3;  // 分页大小
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

十一、为消息定义包

  1. package foo.bar;
    message Open { ... }
    
    • 1
    • 2
  2. 定义了一个包:foo.bar

十二、选项

  1. 下面是一些常用的选项:
    • java_package
      单独为java定义包名字。
    • java_outer_classname
      单独为java定义,protobuf编译器生成的类名。

十三、消息编译成各种语言版本的类库

  1. protoc [OPTION] PROTO_FILES
    
    OPTION是命令的选项, PROTO_FILES是我们要编译的proto消息定义文件,支持多个。
    
    常用的OPTION选项:
     --cpp_out=OUT_DIR           指定代码生成目录,生成 C++ 代码
      --csharp_out=OUT_DIR        指定代码生成目录,生成 C# 代码
      --java_out=OUT_DIR          指定代码生成目录,生成 java 代码
      --js_out=OUT_DIR            指定代码生成目录,生成 javascript 代码
      --objc_out=OUT_DIR          指定代码生成目录,生成 Objective C 代码
      --php_out=OUT_DIR           指定代码生成目录,生成 php 代码
      --python_out=OUT_DIR        指定代码生成目录,生成 python 代码
      --ruby_out=OUT_DIR          指定代码生成目录,生成 ruby 代码
      
      例如:
      protoc --java_out=. demo.proto
      在当前目录导出java版本的代码,编译demo.proto消息。
      有些语言需要单独安装插件才能编译proto,例如golang
      安装go语言的protoc编译器插件
      go get -u github.com/golang/protobuf/protoc-gen-go
      注意: 安装go语言插件后,需要将 $GOPATH/bin 路径加入到PATH环境变量中。
      编译go语言版本
      protoc --go_out=. helloworld.proto
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

十四、go语言使用protobuf

  1. 安装protoc-gen-go,

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

  3. 安装好了之后, 在 G O P A T H / b i n 下面会找到 p r o t o c − g e n − g o ,编译器插件,将 GOPATH/bin下面会找到protoc-gen-go,编译器插件,将 GOPATH/bin下面会找到protocgengo,编译器插件,将GOPATH/bin路径添加到PATH环境变量中

  4. 安装protobuf

  5. go get -u github.com/golang/protobuf

  6. 定义proto消息,件名:score_server/score_info.proto

    syntax = "proto3";
    
    package score_server;
    
    
    // 基本的积分消息
    message base_score_info_t{
    	int32       win_count = 1;                  // 玩家胜局局数
        int32       lose_count = 2;                 // 玩家负局局数
        int32       exception_count = 3;            // 玩家异常局局数
        int32       kill_count = 4;                 // 总人头数
        int32       death_count = 5;                // 总死亡数
        int32       assist_count = 6;               // 总总助攻数
        int64       rating = 7;                     // 评价积分
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  7. 编译proto文件,生成go代码

  8. cd score_server
    protoc --go_out=. score_info.proto
    
    • 1
    • 2
  9. 测试代码

  10. package main
    
    import (
    	"fmt"
            // 导入protobuf依赖包
    	"github.com/golang/protobuf/proto"
           // 导入我们刚才生成的go代码所在的包,注意你们自己的项目路径,可能跟本例子不一样
    	"demo/score_server" 
    )
    
    func main() {
            // 初始化消息
    	score_info := &score_server.BaseScoreInfoT{}
    	score_info.WinCount = 10
    	score_info.LoseCount = 1
    	score_info.ExceptionCount = 2
    	score_info.KillCount = 2
    	score_info.DeathCount = 1
    	score_info.AssistCount = 3
    	score_info.Rating = 120
    
    	// 以字符串的形式打印消息
    	fmt.Println(score_info.String())
    
    	// encode, 转换成二进制数据
    	data, err := proto.Marshal(score_info)
    	if err != nil {
    		panic(err)
    	}
    
    	// decode, 将二进制数据转换成struct对象
    	new_score_info := score_server.BaseScoreInfoT{}
    	err = proto.Unmarshal(data, &new_score_info)
    	if err != nil {
    		panic(err)
    	}
    
    	// 以字符串的形式打印消息
    	fmt.Println(new_score_info.String())
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/907192
推荐阅读
相关标签
  

闽ICP备14008679号