zoukankan      html  css  js  c++  java
  • 1 Protocol Buffers 简介

    1 Protocol Buffers 简介

    protobuf 即 Protocol Buffers,是一种轻便高效的结构化数据存储格式,与语言、平台无关,可扩展可序列化。protobuf 性能和效率大幅度优于 JSON、XML 等其他的结构化数据格式。protobuf 是以二进制方式存储的,占用空间小,但也带来了可读性差的缺点。protobuf 在通信协议和数据存储等领域应用广泛。例如著名的分布式缓存工具 Memcached 的 Go 语言版本groupcache 就使用了 protobuf 作为其 RPC 数据格式。

    Protobuf 在 .proto 定义需要处理的结构化数据,可以通过 protoc 工具,将 .proto 文件转换为 C、C++、Golang、Java、Python 等多种语言的代码,兼容性好,易于使用。

    2 安装

    2.1 protoc

    从 Protobuf Releases 下载最先版本的发布包安装。如果是 Ubuntu,可以按照如下步骤操作(以3.11.2为例)。

    1
    2
    3
    4
    # 下载安装包
    $ wget https://github.com/protocolbuffers/protobuf/releases/download/v3.11.2/protoc-3.11.2-linux-x86_64.zip
    # 解压到 /usr/local 目录下
    $ sudo 7z x protoc-3.11.2-linux-x86_64.zip -o/usr/local

    如果不想安装在 /usr/local 目录下,可以解压到其他的其他,并把解压路径下的 bin 目录 加入到环境变量即可。

    如果能正常显示版本,则表示安装成功。

    1
    2
    $ protoc --version
    libprotoc 3.11.2

    2.2 protoc-gen-go

    我们需要在 Golang 中使用 protobuf,还需要安装 protoc-gen-go,这个工具用来将 .proto 文件转换为 Golang 代码。

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

    protoc-gen-go 将自动安装到 $GOPATH/bin 目录下,也需要将这个目录加入到环境变量中。

    3 定义消息类型

    接下来,我们创建一个非常简单的示例,student.proto

    1
    2
    3
    4
    5
    6
    7
    8
    9
    syntax = "proto3";
    package main;

    // this is a comment
    message Student {
    string name = 1;
    bool male = 2;
    repeated int32 scores = 3;
    }

    在当前目录下执行:

    1
    2
    3
    $ protoc --go_out=. *.proto
    $ ls
    student.pb.go student.proto

    即是,将该目录下的所有的 .proto 文件转换为 Go 代码,我们可以看到该目录下多出了一个 Go 文件 student.pb.go。这个文件内部定义了一个结构体 Student,以及相关的方法:

    1
    2
    3
    4
    5
    6
    type Student struct {
    Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
    Male bool `protobuf:"varint,2,opt,name=male,proto3" json:"male,omitempty"`
    Scores []int32 `protobuf:"varint,3,rep,packed,name=scores,proto3" json:"scores,omitempty"`
    ...
    }

    逐行解读student.proto

    • protobuf 有2个版本,默认版本是 proto2,如果需要 proto3,则需要在非空非注释第一行使用 syntax = "proto3" 标明版本。
    • package,即包名声明符是可选的,用来防止不同的消息类型有命名冲突。
    • 消息类型 使用 message 关键字定义,Student 是类型名,name, male, scores 是该类型的 3 个字段,类型分别为 string, bool 和 []int32。字段可以是标量类型,也可以是合成类型。
    • 每个字段的修饰符默认是 singular,一般省略不写,repeated 表示字段可重复,即用来表示 Go 语言中的数组类型。
    • 每个字符 =后面的数字称为标识符,每个字段都需要提供一个唯一的标识符。标识符用来在消息的二进制格式中识别各个字段,一旦使用就不能够再改变,标识符的取值范围为 [1, 2^29 - 1] 。
    • .proto 文件可以写注释,单行注释 //,多行注释 /* ... */
    • 一个 .proto 文件中可以写多个消息类型,即对应多个结构体(struct)。

    接下来,就可以在项目代码中直接使用了,以下是一个非常简单的例子,即证明被序列化的和反序列化后的实例,包含相同的数据。

    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
    package main

    import (
    "log"

    "github.com/golang/protobuf/proto"
    )

    func main() {
    test := &Student{
    Name: "geektutu",
    Male: true,
    Scores: []int32{98, 85, 88},
    }
    data, err := proto.Marshal(test)
    if err != nil {
    log.Fatal("marshaling error: ", err)
    }
    newTest := &Student{}
    err = proto.Unmarshal(data, newTest)
    if err != nil {
    log.Fatal("unmarshaling error: ", err)
    }
    // Now test and newTest contain the same data.
    if test.GetName() != newTest.GetName() {
    log.Fatalf("data mismatch %q != %q", test.GetName(), newTest.GetName())
    }
    }
    • 保留字段(Reserved Field)

    更新消息类型时,可能会将某些字段/标识符删除。这些被删掉的字段/标识符可能被重新使用,如果加载老版本的数据时,可能会造成数据冲突,在升级时,可以将这些字段/标识符保留(reserved),这样就不会被重新使用了,protoc 会检查。

    1
    2
    3
    4
    message Foo {
    reserved 2, 15, 9 to 11;
    reserved "foo", "bar";
    }

    4 字段类型

    4.1 标量类型(Scalar)

    proto类型go类型备注proto类型go类型备注
    double float64   float float32  
    int32 int32   int64 int64  
    uint32 uint32   uint64 uint64  
    sint32 int32 适合负数 sint64 int64 适合负数
    fixed32 uint32 固长编码,适合大于2^28的值 fixed64 uint64 固长编码,适合大于2^56的值
    sfixed32 int32 固长编码 sfixed64 int64 固长编码
    bool bool   string string UTF8 编码,长度不超过 2^32
    bytes []byte 任意字节序列,长度不超过 2^32      

    标量类型如果没有被赋值,则不会被序列化,解析时,会赋予默认值。

    • strings:空字符串
    • bytes:空序列
    • bools:false
    • 数值类型:0

    4.2 枚举(Enumerations)

    枚举类型适用于提供一组预定义的值,选择其中一个。例如我们将性别定义为枚举类型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    message Student {
    string name = 1;
    enum Gender {
    FEMALE = 0;
    MALE = 1;
    }
    Gender gender = 2;
    repeated int32 scores = 3;
    }
    • 枚举类型的第一个选项的标识符必须是0,这也是枚举类型的默认值。
    • 别名(Alias),允许为不同的枚举值赋予相同的标识符,称之为别名,需要打开allow_alias选项。
    1
    2
    3
    4
    5
    6
    7
    8
    message EnumAllowAlias {
    enum Status {
    option allow_alias = true;
    UNKOWN = 0;
    STARTED = 1;
    RUNNING = 1;
    }
    }

    4.3 使用其他消息类型

    Result是另一个消息类型,在 SearchReponse 作为一个消息字段类型使用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    message SearchResponse {
    repeated Result results = 1;
    }

    message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
    }

    嵌套写也是支持的:

    1
    2
    3
    4
    5
    6
    7
    8
    message SearchResponse {
    message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
    }
    repeated Result results = 1;
    }

    如果定义在其他文件中,可以导入其他消息类型来使用:

    1
    import "myproject/other_protos.proto";

    4.4 任意类型(Any)

    Any 可以表示不在 .proto 中定义任意的内置类型。

    1
    2
    3
    4
    5
    6
    import "google/protobuf/any.proto";

    message ErrorStatus {
    string message = 1;
    repeated google.protobuf.Any details = 2;
    }

    4.5 oneof

    1
    2
    3
    4
    5
    6
    message SampleMessage {
    oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
    }
    }

    4.6 map

    1
    2
    3
    message MapRequest {
    map<string, int32> points = 1;
    }

    5 定义服务(Services)

    如果消息类型是用来远程通信的(Remote Procedure Call, RPC),可以在 .proto 文件中定义 RPC 服务接口。例如我们定义了一个名为 SearchService 的 RPC 服务,提供了 Search 接口,入参是 SearchRequest 类型,返回类型是 SearchResponse

    1
    2
    3
    service SearchService {
    rpc Search (SearchRequest) returns (SearchResponse);
    }

    官方仓库也提供了一个插件列表,帮助开发基于 Protocol Buffer 的 RPC 服务。

    6 protoc 其他参数

    命令行使用方法

    1
    protoc --proto_path=IMPORT_PATH --<lang>_out=DST_DIR path/to/file.proto
    • --proto_path=IMPORT_PATH:可以在 .proto 文件中 import 其他的 .proto 文件,proto_path 即用来指定其他 .proto 文件的查找目录。如果没有引入其他的 .proto 文件,该参数可以省略。
    • --<lang>_out=DST_DIR:指定生成代码的目标文件夹,例如 –go_out=. 即生成 GO 代码在当前文件夹,另外支持 cpp/java/python/ruby/objc/csharp/php 等语言

    7 推荐风格

    • 文件(Files)

      • 文件名使用小写下划线的命名风格,例如 lower_snake_case.proto
      • 每行不超过 80 字符
      • 使用 2 个空格缩进
    • 包(Packages)

      • 包名应该和目录结构对应,例如文件在my/package/目录下,包名应为 my.package
    • 消息和字段(Messages & Fields)

      • 消息名使用首字母大写驼峰风格(CamelCase),例如message StudentRequest { ... }
      • 字段名使用小写下划线的风格,例如 string status_code = 1
      • 枚举类型,枚举名使用首字母大写驼峰风格,例如 enum FooBar,枚举值使用全大写下划线隔开的风格(CAPITALS_WITH_UNDERSCORES ),例如 FOO_DEFAULT=1
    • 服务(Services)

      • RPC 服务名和方法名,均使用首字母大写驼峰风格,例如service FooService{ rpc GetSomething() }

    附:参考

    快速开始使用grpc(golang版)

    你家旭哥 · 2018-12-03 17:34:44 · 13619 次点击 · 预计阅读时间 3 分钟 · 9分钟之前 开始浏览    
    这是一个创建于 2018-12-03 17:34:44 的文章,其中的信息可能已经有所发展或是发生改变。

    开始前准备

    安装gprc

    go get -u google.golang.org/grpc

    安装protocol buffers

    protoc编译器这个用于生成gRPC服务代码的。
    下载解压后放入PATH路径,供后续使用。接下来安装protoc的go语言的插件go get -u github.com/golang/protobuf/protoc-gen-go 。

    注意:这边插件也必须要在PATH路径下

    栗子 采用$GOPATH/src/google.golang.org/grpc/examples/helloworld的栗子

    新建helloworld.proto,这个文件可以供多人使用。

    syntax = "proto3";  //语法声明
    
    package helloworld; //包名
    
    // Greeter 微服务
    service Greeter {
      // Sends a greeting
      rpc SayHello (HelloRequest) returns (HelloReply) {}
    }
    
    // HelloRequest 请求数据格式
    message HelloRequest {
      string name = 1;
    }
    
    // HelloReply 响应数据格式
    message HelloReply {
      string message = 1;
    }
    

    生成golang的服务代码
    protoc -I helloworld/ helloworld/helloworld.proto --go_out=plugins=grpc:helloworld
    这个指令支持*.proto模糊匹配。如果有许多文件可以使用helloworld/*.proto 来作为PROTO_FILES

    服务端代码

    package main
    
    import (
        "context"
        "log"
        "net"
    
        "google.golang.org/grpc"
        pb "google.golang.org/grpc/examples/helloworld/helloworld"
        "google.golang.org/grpc/reflection"
    )
    
    const (
        port = ":50051"
    )
    
    
    type server struct{} //服务对象
    
    // SayHello 实现服务的接口 在proto中定义的所有服务都是接口
    func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
        return &pb.HelloReply{Message: "Hello " + in.Name}, nil
    }
    
    func main() {
        lis, err := net.Listen("tcp", port)
        if err != nil {
            log.Fatalf("failed to listen: %v", err)
        }
        s := grpc.NewServer() //起一个服务 
        pb.RegisterGreeterServer(s, &server{})
        // 注册反射服务 这个服务是CLI使用的 跟服务本身没有关系
        reflection.Register(s)
        if err := s.Serve(lis); err != nil {
            log.Fatalf("failed to serve: %v", err)
        }
    }
    
    

    客户端代码

    package main
    
    import (
        "context"
        "log"
        "os"
        "time"
    
        "google.golang.org/grpc"
        pb "google.golang.org/grpc/examples/helloworld/helloworld"
    )
    
    const (
        address     = "localhost:50051"
        defaultName = "world"
    )
    
    func main() {
        //建立链接
        conn, err := grpc.Dial(address, grpc.WithInsecure())
        if err != nil {
            log.Fatalf("did not connect: %v", err)
        }
        defer conn.Close()
        c := pb.NewGreeterClient(conn)
    
        // Contact the server and print out its response.
        name := defaultName
        if len(os.Args) > 1 {
            name = os.Args[1]
        }
        // 1秒的上下文
        ctx, cancel := context.WithTimeout(context.Background(), time.Second)
        defer cancel()
        r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
        if err != nil {
            log.Fatalf("could not greet: %v", err)
        }
        log.Printf("Greeting: %s", r.Message)
    }
    
    

    总结:grpc所构建的服务可以作为微服务使用,供接口或者其他模块使用。

  • 相关阅读:
    区块链技术的应用场景
    区块链快速了解
    区块链工作汇报部分问题
    区块链Fabric 交易流程
    maven配置settings.xml【阿里云】
    Java常用工具类整理
    程序员职业思考:从大数据到人工智能再到区块链
    Python实现图像信息隐藏
    Python直接控制鼠标键盘
    Mac使用Clion配置OpenGL
  • 原文地址:https://www.cnblogs.com/cheyunhua/p/15305046.html
Copyright © 2011-2022 走看看