proto3基础

proto3基础

参考自官方文档,本文仅仅算是一部分的翻译,读者如果想要深入了解proto3,可能需要使用vpn才可以查看官方文档,本文仅作源文档的参考以助理解与学习。

定义一个消息

每一个proto文件都要指定syntax字段的值指定使用proto的版本。
定义一个消息,使用message关键字。

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

指定字段类型

每一个字段都有自己的字段类型,proto的基础类型的语法类似于众多语言,比如有string,bool,多种整型类型以及其他类型。

字段编号

字段编号用来指定消息中字段的二进制加密格式,1-15编号的字段使用一个字节加密,16-2047编号使用两个字节加密。

在定义proto消息字段时,把经常出现的字段的使用1-15编号标记。

有关proto encoding的更多信息参考

可以指定的最小编码是1,最大编码是2^29-1。在19000-19999之间的字段编码是protobuf保留的编码,不可以用来作为自定义的消息字段的编码。

字段规则

singular:proto3的默认语法。
repeated:该规则标记的字段可以重复任意次。(保留重复值的顺序)

重复的常数类型使用 packed 编码

packed

保留字段

这个 “保留字段” 不是指的某一个语言中的类似保留字、关键字的概念。

在一个.proto文件中,当一个已定义的字段被删除或者注释,后来的用户在更新.proto文件时可能会使用该字段以及该字段的字段编号,这样的话会产生很多问题,如:隐私bug、数据损坏。

此时为了防止当.proto更新时造成这些问题的,protobuf使用reserved标志这些注释或者删除的字段以及字段编号。

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

通过proto编译生成的代码

通过.proto文件定义的消息类型,编译之后生成对应语言的类型,该代码中包含setting/getting/序列化/反序列化

对于不同的语言有不同的代码生成,这里只简要说明一下生成的python代码:

对于python,生成一个对消息类型静态描述的模块,然后在运行时使用元类生成数据访问类。

标量类型

这里列举一下proto中支持的标量类型

double
float
int32
int64

//无符号整数
uint32
uint64

//有符号,可用来加密有负数的整数
sint32  
sint64  

//固定整型,字节数固定,如果整数的取值范围占用字节数固定在4个或者八个字节,可以使用如下的类型
fixed32 //4字节
fixed64 //8字节
sfixed32 //4字节
sfixed64 //8字节

//bool
bool

//string,必须为UTF-8或者ASCII码
string 

//bytes,可以包含任意的bytes序列,长度不超过2^32次方
bytes 

默认值

如果没有指定默认值,则在proto3中,对于singular每个类型的默认值如下

string -> ""
bytes -> ""
bool -> false
numeric -> 0
enum -> 第一个值
message -> 不同的语言指定的默认值不同,比如python 可能为 list,dict等

对于repeated,都是空列表或空数组,视语言而定。

枚举

枚举

枚举类型的值需要从0开始。
枚举类型的值大小不得超过4个字节,因为枚举类型的值使用变量编码
负数的效率低下,不建议使用负数。
枚举可以定义在message中或者定义在message外面。

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  //Corpus的定义
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  //create a obj of Corpus on message.
  Corpus corpus = 4;
}

允许枚举字段值重复,需要加上option allow_alias = true;(否则编译报错),如下允许STARTED,RUNNING的值重复。

message MyMessage1 {
  enum EnumAllowingAlias {
    option allow_alias = true;
    UNKNOWN = 0;
    STARTED = 1;
    RUNNING = 1;
  }
}

对于特定语言的枚举中无法识别的值,参考更多

保留值

类似于保留字段,删除或者注释掉的枚举字段名以及枚举字段值需要作为保留值,防止将来update使用这些枚举字段名以及枚举字段值以至于出现安全问题。
指定数字的范围使用 to

enum Foo {
  reserved 2, 15, 9 to 11, 40 to max;
  reserved "FOO", "BAR";
}

其他消息类型

使用消息类型作为字段类型

message SearchResponse {
  repeated Result results = 1;
}

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

import的使用场景

用法:import "protofile_path"
使用场景:

  1. 假如现在有两个.proto文件1.proto,2.proto,分别定义了两个message:one,two,你在1.proto中想要引用2.proto中的消息字段:
# import 2.proto into 1.proto 
import "./2.proto"
  1. 假如你在old/下有一个old.proto文件,别的文件引用了该文件中的message,而你想把old.proto文件换个位置,放到new/下,此时需要在old/再创建一个old.proto文件,包含如下内容:

old/old.proto文件中的内容:

import "./new/old.proto"

–proto_path / -I指定的是proto文件的源目录,一般使用项目的根目录,如果不指定,则在当前目录下寻找proto文件。因为可能会引入多个不容的proto文件,所以-I参数的指定还是挺重要的。

嵌套类型

在消息A中定义消息B,且创建消息B的对象作为消息A的字段使用:

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

如果此时你想让消息B在消息A外部被使用,则如下形式_Parent_._Type_

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}

你可以随心所欲的多层嵌套消息,only you feel happy:

message Outer {                  // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      int64 ival = 1;
      bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      int32 ival = 1;
      bool  booly = 2;
    }
  }
}

更新消息类型

更新消息类型的主要应用场景是:在旧的消息上添加新的字段,需要遵守以下规则:

  • 不改变已经存在的字段
  • 由新代码创建的消息可以被旧代码解析(旧代码在解析时,忽略新增的字段)
  • 字段可以被删除,只要新更新的消息中不再需要该字段(删除时要伴随着把该字段作为保留字段)
  • int32 / int64 / uint32 / uint64 / bool互相兼容,意味着他们可以从一种类型被修改成另外一种类型。
  • sint32 / sint64互相兼容,同上
  • string / bytes互相兼容,同上
  • fixed32 / sfixed32,同上
  • fixed64 / sfixed64,同上
  • 如果bytes包含message的编码版本,则嵌入消息与字节兼容
  • 对于bytes和string以及message,optional和repeated互相兼容;给定一个输入时repeated数据,客户端期望的是optional,则此时会取repeated的最后一个值。
  • 枚举和(有无符号的32/64位)整型互相兼容。
  • enum is compatible with int32, uint32, int64, and uint64 in terms of wire format (note that values will be truncated if they don’t fit). However be aware that client code may treat them differently when the message is deserialized: for example, unrecognized proto3 enum types will be preserved in the message, but how this is represented when the message is deserialized is language-dependent. Int fields always just preserve their value.
  • Changing a single value into a member of a new oneof is safe and binary compatible. Moving multiple fields into a new oneof may be safe if you are sure that no code sets more than one at a time. Moving any fields into an existing oneof is not safe

未知字段

例如当proto文件更新之后,旧的代码解析新格式的数据,新格式的数据中的字段就会被忽略不解析。

任意字段 Any

如下是设置了detail为一个任意类型的字段

import "google/protobuf/any.proto";

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

Oneof类型

Oneof标记的类型,包含很多字段,同一时间,只能操作一个字段。

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}

除了map和repeated,你可以添加任何的字段到Oneof中。

Oneof类型的特征

  • 如果多次设置了Oneof指定的变量的值,最后设置的值才是最终值。
  • 如果解析器在网络上遇到同一个对象的多个成员,则在解析的消息中仅使用最后看到的成员
  • Oneof类型不能repeated
  • 反射API适用于其中一个领域
  • 如果为一个Oneof类型的变量中的字段设置值,当前被选中的字段将会被设置值,且会在线路上被序列化。
  • 可以使用swap()使两个不同的Oneof对象交换值。

向后兼容性

在删除或者添加Oneof变量字段时,如果检查这个值之后返回的是一个None/NOT_SET,这意味着这个值还没有设置或者设置了但是却是另一个Oneof变量的版本;没有方式能够知道一个未知字段是否是Oneof的一个成员。

Tag重用问题

  1. move/delete and add it back / split and merge

以上都会造成一些问题

Maps

定义Map的语法简单:
map<key_type, value_type> map_field = N;

key_type:除了浮点型和bool型,都可以作为key
value_type:可以为任何类型,除了map

当从wire中解析或者合并,最后一个值被使用;当从一个文本中解析时,如果有重复的key,将会解析错误。

向后兼容性

这种写法不使用map类型,依旧可以实现map的作用。

message MapFieldEntry {
  key_type key = 1;
  value_type value = 2;
}

repeated MapFieldEntry map_field = N;

任何支持映射的protobuf实现都必须产生并接受上述定义可以接受的数据。

package

使用package,避免命名冲突。
比如:我在foo/bar目录下创建一个.proto文件,如下所示是我message的名字

package foo.bar;
message Open { ... }

在使用的时候,我就可以如下根据包的名字来引入我的对应包路径下定义的message

message Foo {
  ...
  foo.bar.Open open = 1;
  ...
}

定义service

如下所示,以service关键字定义服务。

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

JSON

proto3支持JSON的编码规范。
如果json格式的数据种缺少值或者值为空,那么序列化时,会被赋为默认值。当proto字段使用默认值时,在反序列化时,会被省略。
在这里插入图片描述
在这里插入图片描述

  • Proto3 JSON解析器默认情况下应拒绝未知字段,但可以提供在解析时忽略未知字段的选项
  • 默认情况下,proto3 JSON输出中会省略具有默认值的字段
  • 将枚举值作为整数而不是字符串发送

选项

proto文件中的各个声明可以使用许多选项进行注释,该举动不会影响整体的意思,但可能会影响在特定情况下处理他们的方式。这些可用的选项定义在google/protobuf/descriptor.proto.下。
这里简单介绍几个可选项操作(没有介绍只针对java相关的可选项操作):

  • optimize_for
    • SPEED:这个模式下生成的代码已经高度优化,速度快
    • CODE_SIZE:这个模式下的代码非常精简,但是速度相比SPEED慢。适用于生成许多API且不盲目的只追求运行速度时使用。
    • LITE_RUNTIME:生成仅依赖于“ lite”运行时库的类,lite_runtime比完整的库代码要精简,但是省略了某些特征,比如描述符和反射。对于受限的平台非常有用。同SPEED模式一样,生成速度比较快的代码。生成的类将以特定的语言实现MessageLite接口。它仅提供完整消息接口方法的一部分。
option optimize_for = CODE_SIZE;
  • deprecated:使用这个可选项时,代表该字段被废弃,不应该被新代码使用,在大多数语言种,这个可选项没有效果,在java种,这个选项使用@Deprecated注解实现。一般使用保留字段或者保留值代替该可选项使用。

自定义可选项

目前只有proto3支持自定义可选项,这里不介绍,参考此处

生成代码

使用proto的最后一步就是生成对应语言的代码。
使用proto的编译器protoc编译.proto文件;

go语言使用proto安装指导

protoc生辰指定代码的命令示例:
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto

  • IMPORT_PATH:指定.proto文件的文件夹,如果省略,则默认使用当前文件夹。如果有多个存储.proto文件的路径,可以多次使用--proto_path这个参数。-I--proto_path作用相同。

指定不同语言生成的代码的存储路径。
在这里插入图片描述

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue3可以使用Protobuf格式的数据进行WebSocket传输。首先,你需要安装基础库。然后,你可以按照以下步骤实现具体的代码: 1. 服务端编写message文件定义。在这个文件中,你可以使用proto3语法来定义消息的结构。例如,你可以定义一个消息类型为Message,其中包含一个名称为text的字符串字段。 2. 客户端代码可以通过两种方法来实现。方法1是使用Vue3的内置支持来处理Protobuf数据。方法2是使用第三方库来处理Protobuf数据。你可以选择适合你项目的方法。 以上是使用Protobuf的基本步骤,你可以根据你的实际需求进行详细实现。如果你需要完整示例代码,可以参考中提供的代码。另外,中的示例代码展示了如何在message.proto中定义不同类型的字段,例如字符串、浮点数、布尔值,以及复杂的嵌套消息类型和重复字段。 总之,Vue3可以使用Protobuf格式的数据进行WebSocket传输,你可以根据需要选择合适的方法来实现。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [vue3项目使用WebSocket 传输 Protobuf 格式的数据](https://blog.csdn.net/qq_43399210/article/details/129657443)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [Vue中使用protobuf](https://blog.csdn.net/qq_40323256/article/details/123809155)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值