zoukankan      html  css  js  c++  java
  • ProtoBuf 语法学习笔记

     
     
    上一节学习了protoc命令的用法,以及配合生成grpc的相关。这一节,来学习一下proto文件中的语法规则。

    protoc 命令通过解析 *.proto文件,来生成对应语言的服务文件。

    看一个例子:

    syntax = "proto3";
    package proto;
    option go_package = ".;proto";
    message User {
        string name=1;
        int32 age=2;
    }
    message Id {
        int32 uid=1;
    }
    //要生成server rpc代码
    service ServiceSearch{
        rpc SaveUser(User) returns (Id){}
        rpc UserInfo(Id) returns (User){}
    }
    

      

    可以看出,它的语法结构有点像Makefile风格。

     

    官网文档

    https://developers.google.com/protocol-buffers/docs/proto3

    官网文档很全,可以对应的学习。但是很多东西,可能并没有用到,全部学习一边,第一是很累,第二,新人上手,很容易受挫,所以,我直接跳过一些非常基础的,也跳过一些不常用的,本次就学一下常用的一些元素,直接入题。

     

    0. 反人类

    protobuf 是一个跨平台的结构数据序列化方法的工具,我们使用之前,得把我们接口里用到的所有数据,在proto文件里先定义出来

    这意味着,我们得把接口输入输出的所有的数据字段,都得定义在proto文件里,这一点真的是蛋疼!

    比如,我一个接口,吐出的数据是一张表,里面有100个字段,那么,就得先在proto文件里讲这100个字段,用message的方式给定义出来。

    这。。。。

    所以,难在如何把自己的实际业务接口数据抽象出来,匹配上protobuf的语法,我认为这个是最难的。

     

    1. message

    我们初学时,看到的proto文件的例子基本都是从mesage开始的。可以说,它是最基础的,也是90%用的最多的元素了。那么啥是message呢?字面翻译为消息,那么啥是消息呢?

    在讲消息之前,我们先回忆一下,普通的接口输出Json格式的数据是啥样的,这样会更好的利于我们理解message:

     
    {
        "name": "james",
        "age": 18
    }
    

      

    上面是我们调用/UserInfo?id=1接口,输出了用户的信息,是一个json格式的数据,里面有2个参数:姓名和年龄。

    由于proto的特性,那么我们就得提前在proto文件里定义一下这2个元素:

     

    syntax = "proto3";
    message User {
        string name=1;
        int32 age=2;
    }
    

      

    我们先不看这一段具体是啥意思,我们先分别用PHP和golang转换输出一下,看下转换后的语言结构是咋样的:

    protoc --php_out=:. hello.proto
    

      

    先看PHP生成后的文件长啥样?它会自动在本目录下生成了2个文件夹:ProtoGPBMetadata

     
    1. ├── GPBMetadata
    2.    └── Hello.php
    3. ├── Proto
    4.    └── User.php
    5. ├── hello.proto

    Hello.php的内容为:

    <?php
    # Generated by the protocol buffer compiler.  DO NOT EDIT!
    # source: hello.proto
    namespace GPBMetadata;
    class Hello
    {
        public static $is_initialized = false;
        public static function initOnce() {
            $pool = GoogleProtobufInternalDescriptorPool::getGeneratedPool();
            if (static::$is_initialized == true) {
              return;
            }
            $pool->internalAddGeneratedFile(hex2bin(
                "0a3f0a0b68656c6c6f2e70726f746f120570726f746f22210a0455736572" .
                "120c0a046e616d65180120012809120b0a03616765180220012805620670" .
                "726f746f33"
            ), true);
            static::$is_initialized = true;
        }
    }
    
    

      

    咋一眼看,只知道是一个单例模式,具体也不清楚它是啥作用的。不过不要急,继续看User.php文件

    <?php
    # Generated by the protocol buffer compiler.  DO NOT EDIT!
    # source: hello.proto
    namespace Proto;
    use GoogleProtobufInternalGPBType;
    use GoogleProtobufInternalRepeatedField;
    use GoogleProtobufInternalGPBUtil;
    /**
     * Generated from protobuf message <code>proto.User</code>
     */
    class User extends GoogleProtobufInternalMessage
    {
        /**
         * Generated from protobuf field <code>string name = 1;</code>
         */
        protected $name = '';
        /**
         * Generated from protobuf field <code>int32 age = 2;</code>
         */
        protected $age = 0;
        /**
         * Constructor.
         *
         * @param array $data {
         *     Optional. Data for populating the Message object.
         *
         *     @type string $name
         *     @type int $age
         * }
         */
        public function __construct($data = NULL) {
            GPBMetadataHello::initOnce();
            parent::__construct($data);
        }
        /**
         * Generated from protobuf field <code>string name = 1;</code>
         * @return string
         */
        public function getName()
        {
            return $this->name;
        }
        /**
         * Generated from protobuf field <code>string name = 1;</code>
         * @param string $var
         * @return $this
         */
        public function setName($var)
        {
            GPBUtil::checkString($var, True);
            $this->name = $var;
            return $this;
        }
        /**
         * Generated from protobuf field <code>int32 age = 2;</code>
         * @return int
         */
        public function getAge()
        {
            return $this->age;
        }
        /**
         * Generated from protobuf field <code>int32 age = 2;</code>
         * @param int $var
         * @return $this
         */
        public function setAge($var)
        {
            GPBUtil::checkInt32($var);
            $this->age = $var;
            return $this;
        }
    }
    

      

    这个php文件里面的生成的几个方法很清晰,方别是采用链式结构设置和读取类的成员变量 和age 的值。看来,在PHP里面,protobuf的处理方式是用面向对象的方式来处理数据。1个message,就会生成1个类,这个message里的每一个字段,就会变成php对象里的成员属性。然后再自动生成相同数量的get 和 set方法,来获取和设置每一个成员熟悉的值。嗯。看上去虽然有点繁琐,但是基本也能看的懂。

    我们接着看下,golang里面,自动生成的文件是怎样的。

     
    1. protoc --go_out=:. hello.proto

    会在本目录下,生成1个hello.pb.go的文件:

     
    // Code generated by protoc-gen-go. DO NOT EDIT.
    // versions:
    //  protoc-gen-go v1.21.0
    //  protoc        v3.11.3
    // source: hello.proto
    package proto
    import (
        proto "github.com/golang/protobuf/proto"
        protoreflect "google.golang.org/protobuf/reflect/protoreflect"
        protoimpl "google.golang.org/protobuf/runtime/protoimpl"
        reflect "reflect"
        sync "sync"
    )
    const (
        // Verify that this generated code is sufficiently up-to-date.
        _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
        // Verify that runtime/protoimpl is sufficiently up-to-date.
        _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
    )
    // This is a compile-time assertion that a sufficiently up-to-date version
    // of the legacy proto package is being used.
    const _ = proto.ProtoPackageIsVersion4
    type User struct {
        state         protoimpl.MessageState
        sizeCache     protoimpl.SizeCache
        unknownFields protoimpl.UnknownFields
        Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
        Age  int32  `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
    }
    func (x *User) Reset() {
        *x = User{}
        if protoimpl.UnsafeEnabled {
            mi := &file_hello_proto_msgTypes[0]
            ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
            ms.StoreMessageInfo(mi)
        }
    }
    func (x *User) String() string {
        return protoimpl.X.MessageStringOf(x)
    }
    func (*User) ProtoMessage() {}
    func (x *User) ProtoReflect() protoreflect.Message {
        mi := &file_hello_proto_msgTypes[0]
        if protoimpl.UnsafeEnabled && x != nil {
            ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
            if ms.LoadMessageInfo() == nil {
                ms.StoreMessageInfo(mi)
            }
            return ms
        }
        return mi.MessageOf(x)
    }
    // Deprecated: Use User.ProtoReflect.Descriptor instead.
    func (*User) Descriptor() ([]byte, []int) {
        return file_hello_proto_rawDescGZIP(), []int{0}
    }
    func (x *User) GetName() string {
        if x != nil {
            return x.Name
        }
        return ""
    }
    func (x *User) GetAge() int32 {
        if x != nil {
            return x.Age
        }
        return 0
    }
    var File_hello_proto protoreflect.FileDescriptor
    var file_hello_proto_rawDesc = []byte{
        0x0a, 0x0b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70,
        0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2c, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04,
        0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
        0x12, 0x10, 0x0a, 0x03, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61,
        0x67, 0x65, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70,
        0x72, 0x6f, 0x74, 0x6f, 0x33,
    }
    var (
        file_hello_proto_rawDescOnce sync.Once
        file_hello_proto_rawDescData = file_hello_proto_rawDesc
    )
    func file_hello_proto_rawDescGZIP() []byte {
        file_hello_proto_rawDescOnce.Do(func() {
            file_hello_proto_rawDescData = protoimpl.X.CompressGZIP(file_hello_proto_rawDescData)
        })
        return file_hello_proto_rawDescData
    }
    var file_hello_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
    var file_hello_proto_goTypes = []interface{}{
        (*User)(nil), // 0: proto.User
    }
    var file_hello_proto_depIdxs = []int32{
        0, // [0:0] is the sub-list for method output_type
        0, // [0:0] is the sub-list for method input_type
        0, // [0:0] is the sub-list for extension type_name
        0, // [0:0] is the sub-list for extension extendee
        0, // [0:0] is the sub-list for field type_name
    }
    func init() { file_hello_proto_init() }
    func file_hello_proto_init() {
        if File_hello_proto != nil {
            return
        }
        if !protoimpl.UnsafeEnabled {
            file_hello_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
                switch v := v.(*User); i {
                case 0:
                    return &v.state
                case 1:
                    return &v.sizeCache
                case 2:
                    return &v.unknownFields
                default:
                    return nil
                }
            }
        }
        type x struct{}
        out := protoimpl.TypeBuilder{
            File: protoimpl.DescBuilder{
                GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
                RawDescriptor: file_hello_proto_rawDesc,
                NumEnums:      0,
                NumMessages:   1,
                NumExtensions: 0,
                NumServices:   0,
            },
            GoTypes:           file_hello_proto_goTypes,
            DependencyIndexes: file_hello_proto_depIdxs,
            MessageInfos:      file_hello_proto_msgTypes,
        }.Build()
        File_hello_proto = out.File
        file_hello_proto_rawDesc = nil
        file_hello_proto_goTypes = nil
        file_hello_proto_depIdxs = nil
    }
    

      

    大致看了一眼,乱七八糟的也不知道都是啥意思,但是不要急,看关键的这段就行:

    type User struct {
        state         protoimpl.MessageState
        sizeCache     protoimpl.SizeCache
        unknownFields protoimpl.UnknownFields
        Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
        Age  int32  `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
    }
    

      

    我们知道了,golang里面,是以结构体的方式来对接这个message的。message里的每一个字段,都演变成struct里的子元素。这样就可以了。

    好了。对于message有1个大致的映像就可以了。我们继续讲message的语法

     
    syntax = "proto3";
    message User {
        string name=1;
        int32 age=2;
    }
    

      

    开头的syntax = "proto3";是来申明这个文件是基于ptoro3语法规则的,有点类似于Python3的文件头部#!/usr/bin/python3申明一样。看了官方文章介绍说,之前也有proto2的版本,protoc解释器默认是proto2的语法。但是,我把proto3改成proto2,执行,却报错了。迷惑不解。

    所以,就不纠结是2还是3,直接写3就完事了。但是这一行内容,得写在文件的最开始。

    message 后面是消息体的名字, 命名规则是首字母大写,大驼峰的方式。如果不是大写,转换成go语言的时候,会把自动把首首字母变成大写。,转成成php语言,不会大小写转换,其他语言暂不清楚。

     
    #转换go
    string name_a=1;  ===>      NameA   string
    string name_A=1;  ===>      Name_A   string
    string nameA=1;  ===>       NameA   string
    

      

    花括号里面,就是具体的字段了:

    string name=1;
    int32 age=2;
    

      

    大致看一下,是先定义字段的类型,是字符型还是整型,这个好理解,可是名字后面的=1,=2 是什么鬼???是说,name 赋值为1,age 赋值为2 吗?

    然而,并不是这样,也并没有我们想的这么简单。查看一下官方文档关于这个数字标识:

    消息请求结构体中每个字段都有唯一的数字标识,这些标识用来在消息的二进制格式中识别你的字段,并且一旦消息投入使用,这些标识就不应该再被修改。
    标识1-15使用一个字节编码,包括数字和字段类型;标识16-2047使用两个字节编码。所以应该保留1-15,用作出现最频繁的消息类型的标识,不要把1-15用完,为以后留点。

    所以,对于新手而已,怎么用呢?我的理解就是随便,只要不重复就好了:

     
    message user1 {
        string name_a=1;
        int32 age_1=2;
        string address=5;
        int32 gender=3;
    }
    message user2 {
        string name_a=1;
        int32 age_1=2;
        string address=50;
        int32 gender=4;
    }
    

      

    对于新手而已,不要过多的被这个特性给吓唬住了,等你熟练使用了,再去思考他的原理。

    message里面的字段的类型有多种,上面列出了2种,string,int32,同时还有这些:

    数字类型: double、float、int32、int64、uint32、uint64、sint32、sint64: 存储长度可变的浮点数、整数、无符号整数和有符号整数
    存储固定大小的数字类型:fixed32、fixed64、sfixed32、sfixed64: 存储空间固定
    布尔类型: bool
    字符串: string
    bytes: 字节数组
    message: 消息类型
    enum :枚举类型

    我们可以根据我们实际的情况,来定义它们的字段类型。

    下面我们来看其他重要的点。

     

    2. package

    我们在定义一个.proto文件时,需要申明这个文件属于哪个包,主要是为了规范整合以及避免重复,这个概念在其他语言中也存在,比如php中的namespace的概念,go中的 package概念。

    所以,我们根据实际的分类情况,给每1个proto文件都定义1个包,一般这个包和proto所在的文件夹名子,保持一致。

    比如例子中,文件在proto文件夹中,那我们用的package 就为: proto;

     

    3. option

    看这个名字,就知道是选项和配置的意思,常见的选项是配置 go_package

     
    1. option go_package = ".;proto";

    现在protoc命令生成go包的时候,如果这一行没加上,会提示错误:

     
    1. proto git:(master) protoc --go_out=:. hello.proto
    2. 2020/05/21 15:59:40 WARNING: Missing 'go_package' option in "hello.proto", please specify:
    3. option go_package = ".;proto";
    4. A future release of protoc-gen-go will require this be specified.
    5. See https://developers.google.com/protocol-buffers/docs/reference/go-generated#package for more information.

    所以,这个go_package和上面那个package proto;有啥区别呢?有点蒙啊。

    我尝试这样改一下:

    syntax = "proto3";
    package protoB;
    option go_package = ".;protoA";

    我看下,生成的go语言包的package到底是啥?打开,生成后的go文件:

     
    1. # vi hello.pb.go
    2. package protoA
    3. ...

    发现是protoA,说明,go的package是受option go_package影响的。所以,在我们没有申请这一句的时候,系统就会用proto文件的package名字,为提示,让你也加上相同的go_package名字了。

    再来看一下,这个=".;proto" 是啥意思。我改一下:

     
    1. option go_package = "./protoA";

    执行后,发现,生成了一个protoA文件夹。里面的hello.pb.go文件的package也是protoA。

    所以,.;表示的是就在本目录下的意思么???行吧。

    再来看一下,我们改成1个绝对的路径目录:

     
    1. option go_package = "/";

    所以,总结一下:

     
    1. package protoB; //这个用来设定proto文件自身的package
    2. option go_package = ".;protoA"; //这个用来生成的go文件package。一般情况下,可以把这2个设置成一样
     

     源:https://www.zybuluo.com/phper/note/1703429

    +
     
     
  • 相关阅读:
    给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
    11
    实战 迁移学习 VGG19、ResNet50、InceptionV3 实践 猫狗大战 问题
    tx2系统备份与恢复
    如何在Ubuntu 18.04上安装和卸载TeamViewer
    bzoj 3732 Network (kruskal重构树)
    bzoj2152 聪聪可可 (树形dp)
    牛客 216D 消消乐 (二分图最小点覆盖)
    牛客 197E 01串
    Wannafly挑战赛23
  • 原文地址:https://www.cnblogs.com/a00ium/p/14166118.html
Copyright © 2011-2022 走看看