gRPC-Go基础(2)protobuf基础

文章目录

  • 0. 简介
  • 1. Message
  • 2. Message中的字段
  • 3. 枚举类型
  • 4. Map类型
  • 5. Oneof类型
  • 6. Any类型
  • 7. Struct类型

0. 简介

前面讲过protobuf工具protoc的使用,本章我们将简单介绍一下protobuf的语法和相关细节。

1. Message

protobuf实际上是一套类似于Json或者XML的数据传输格式和规范,用于不同应用、平台之间的通信,而message就是作为protobuf中定义通信的数据格式。如下,我们可以定义一个message结构:

message Foo {}

在protoc-gen-go插件的作用下,会生成一个名为Foo的结构体,且*Foo会实现proto.Message接口,生成的pg.go文件如下:

type Foo struct {state         protoimpl.MessageStatesizeCache     protoimpl.SizeCacheunknownFields protoimpl.UnknownFields
}func (x *Foo) Reset() {*x = Foo{}if protoimpl.UnsafeEnabled {mi := &file_simplepb_simple_proto_msgTypes[0]ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))ms.StoreMessageInfo(mi)}
}func (x *Foo) String() string {return protoimpl.X.MessageStringOf(x)
}func (*Foo) ProtoMessage() {}func (x *Foo) ProtoReflect() protoreflect.Message {mi := &file_simplepb_simple_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)
}

其中,Reset()、String()和ProtoMessage()都是MessageV1定义的接口,在我们的版本中并用不到,为了兼容,生成器还是生成了这部分接口实现。

type MessageV1 interface {Reset()String() stringProtoMessage()
}

而实际中真正用到的是MessageV2,这个接口定义了一个ProtoReflect()方法,此方法返回一个名为protoreflect.Message的接口,这个接口提供基于反射的message接口定义。

2. Message中的字段

上面简单介绍了结构体的生成,接下来介绍一下结构体中的字段。比如人员描述的相关对象如下:

message Profile {string name = 1;int32 age = 2;
}

2.1 字段编号
message中的每一个字段都有一个唯一的编号,编号的范围从1到229-1,这些编号用于在编码后的二进制中标识字段,对于这些编号,特别是对于与外部平台或者设备交互的接口,一旦确定就不能更改,否则可能造成不同版本之间的不兼容。
需要注意的是,当字段编号在1-15时,在protobuf的编码规则下,其只需一个byte即可编码编号和类型;而当字段编号在16-2047时,就需要两个byte了。所以针对高并发、频繁调用的数据接口,最好使用字段1-15。
另外,我们不能使用数字19000-19999作为字段编号,这是为protobuf保留的。
2.2 字段规则
字段的规则可以有以下几种:

  • singular:对proto3而言,当未设置其他规则时,这就是默认规则;如果是零值时,将不会被序列化,所以对于接收到的默认零值,我们无法判断这是对方设置的还是默认生成的。在go中,这倒并不影响,对于非message字段(message类型也可以作为message中的字段),其他所有singular规则字段都是非指针的,默认就是零值,和proto是对应的,但是proto中的零值和不设置需要表现出一致的含义。
  • optional:和singular相同,只是可以检查该值是否明确;当该字段被设置时,它可以被正常的序列化和反序列化;当未设置时,会返回默认值,则不会被序列化;对于go而言,optional规则的字段会被指针化,其和proto定义是一致的,不设置和设置为零值可以表现出两个含义。
  • repeated:重复值,映射到go中就是切片。
  • map:键值对类型,映射到go中就是map。

如下,我们设置以下的字段规则;其生成的pb.go中结构体也不尽相同。
处。

message Profile {string name = 1;optional int32 age = 2;repeated string hobbies = 3;map<int32,int32> calender = 4;
}

生成的pb.go文件如下:

type Profile struct {state         protoimpl.MessageStatesizeCache     protoimpl.SizeCacheunknownFields protoimpl.UnknownFieldsName     string          `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`Age      *int32          `protobuf:"varint,2,opt,name=age,proto3,oneof" json:"age,omitempty"`Hobbies  []string        `protobuf:"bytes,3,rep,name=hobbies,proto3" json:"hobbies,omitempty"`Calender map[int32]int32 `protobuf:"bytes,4,rep,name=calender,proto3" json:"calender,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
}

2.3 保留字段
如果我们更改了接口,删除了一些不再使用的字段,那么后来者可能会使用这些字段表示另外一些含义,对于一些对外的接口,可能引起版本之间的不兼容,可能会引起数据损坏、隐私暴露等严重错误,所以我们使用reserve字段来保留这些字段,防止错误的发生,如下:

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

2.4 字段类型
字段的类型众多,和各大语言之间的对应关系如:Scalar Value Types
2.5 字段默认值
对于编码后的数据而言,如果singular字段没有被设置值,那么相应字段会被设置为该字段的默认值。不同类型的字段有着不同的默认值:

  • string:默认零值是空字符串,go中是"";
  • bytes:默认是空字节数组,go中是[]byte(nil);
  • bool:默认是否,go中是false;
  • numeric:数值类型,都是0;
  • enum:枚举值,其默认值一定是其定义的第一个枚举量,必然为0;
  • message:对于以message作为字段类型的,对于不同的语言其表现是不同的,在go语言中,其被映射为结构体指针,所以其零值是结构体的nil。

需要注意的是,当接收方拿到零值类型对象的时候,无法判断这个对象是由对方设置了零值,还是没有设置任何值,所以,不要用零值去触发一些特定的行为,可能会发生不可预计的事情。在go中,如果需要区分设置了零值和没有设置任何值,可以用optional规则。
另外,零值是不会被序列化的。

3. 枚举类型

message Profile {string name = 1;optional int32 age = 2;repeated string hobbies = 3;map<int32,int32> calender = 4;enum Gender {UNKNOWN = 0;MALE = 1;FEMALE = 2;}Gender gender = 5;
}

如上,gender字段就是枚举类型,其有以下两个特点:

  • 枚举类型必须有一个零值枚举,且这个零值枚举是枚举值的默认值;
  • 零值必须是第一个枚举,以兼容proto2中第一个枚举值肯定是默认值的语义。

可以使用option allow_alias = true使用枚举别名,如下,这时候,MAN和MALE就互为别名。

enum Gender {option allow_alias = true;UNKNOWN = 0;MAN = 1;MALE = 1;WOMAN = 2;FEMALE = 2;
}

值得注意的是,枚举常量是32位的int常量,因为其使用变长编码,所以使用负数作为枚举值的序列化效率是很低的。

4. Map类型

protobuf的map类型定义如下,其中,key_type可以是字符串或者整型,value_type可以是除了另一个map外的所有类型。

map<key_type, value_type> map_field = N;

5. Oneof类型

如果对于消息中,有多个字段,同时至多只能选择一个字段的,可以使用oneof类型,oneof类型的字段中所有字段共享内存,如果一个字段被设置,那么其他字段自动被删除。如果设置了多个值,那么最后一个设置的值将覆盖前面的值。
如下,我们可以设置Profile的头像Avatar如下,其头像可以是图片存储的url,也可以直接是图片数据,这里我们就可以使用oneof类型。

package account;message Profile {string name = 1;int32 age = 2;repeated string hobbies = 3;map<int32,int32> calender = 4;enum Gender {option allow_alias = true;UNKNOWN = 0;MAN = 1;MALE = 1;WOMAN = 2;FEMALE = 2;}Gender gender = 5;oneof avatar {string image_url = 6;bytes image_data = 7;}
}

设置值的时候可以这样写:

p1 := &account.Profile{Avatar: &account.Profile_ImageUrl{"http://example.com/image.png"},
}// imageData is []byte
imageData := getImageData()
p2 := &account.Profile{Avatar: &account.Profile_ImageData{imageData},
}

解析字段的时候可以这样写:

switch x := m.Avatar.(type) {
case *account.Profile_ImageUrl:// Load profile image based on URL// using x.ImageUrl
case *account.Profile_ImageData:// Load profile image based on bytes// using x.ImageData
case nil:// The field is not set.
default:return fmt.Errorf("Profile.Avatar has unexpected type %T", x)
}

6. Any类型

Any类型允许我们使用此字段传递任何一个protobuf类型的消息,类似于某些语言中泛型,如下,其只有两个字段,value字段是序列化后的二进制值,type_url则是该类型的唯一标识符,在golang中,所有message的唯一标识符都可以通过反射m.ProtoReflect().Descriptor().FullName()拿到。
为了使用any类型,我们需要import “google/protobuf/any.proto”。proto文件的下载地址为链接。

anymessage Any {// ...string type_url = 1;// Must be a valid serialized protocol buffer of the above specified type.bytes value = 2;
}

golang库的底层提供了一些函数用于发送或者接收的函数,譬如,发送时,我们可以使用以下方式序列化我们希望序列化的消息:

detail, err := anypb.New(&AnyA{})

解析接收数据时,我们可以用以下方法判断类型或者反序列化消息:

// MessageIs 判断是否为此message类型
m.Detail.MessageIs((*AnyA)(nil))// UnmarshalTo 将any解析到具体类型
var a AnyA
err := m.Detail.UnmarshalTo(&a)// UnmarshalNew 将消息类型转回动态类型
dy, err := m.Detail.UnmarshalNew()
_, ok := dy.(*AnyA)

7. Struct类型

struct类型和any类型是类似的,都是protobuf提供的不透明配置嵌入的方式。不同的是,any是将带有类型信息的二进制序列化的protobuf嵌入到另一个protobuf的字段内;而struct是最外层不能是数组的动态json类型。

message Struct {// Unordered map of dynamically typed values.map<string, Value> fields = 1;
}// JSON 数据类型
message Value {// The kind of value.oneof kind {// Represents a null value.NullValue null_value = 1;// Represents a double value.double number_value = 2;// Represents a string value.string string_value = 3;// Represents a boolean value.bool bool_value = 4;// Represents a structured value.Struct struct_value = 5;// Represents a repeated `Value`.ListValue list_value = 6;}
}// JSON null
enum NullValue {// Null value.NULL_VALUE = 0;
}// JSON 数组
message ListValue {repeated Value values = 1;
}

同样的,为了使用struct类型,我们需要import “google/protobuf/struct.proto”。proto文件的下载地址为链接。

我们也可以使用以下的方法去创建struct类型:

// NewStruct 从map表创建struct
s, err := structpb.NewStruct(m)// UnmarshalJSON 从json串生成strcut
s := &_struct.Struct{}
marshal, _ := json.Marshal(data)
err := s.UnmarshalJSON(marshal)

可以使用以下的方法去解析接收到的strcut类型数据:

// AsMap 将strcut转换成map
m := s.AsMap()// MarshalJSON 将struct转换为[]byte
data, err := s.MarshalJSON()
_ = json.Unmarshal(data, v)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/581129.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《微信小程序开发从入门到实战》学习六十四

6.4 交互API 6.4.3 操作菜单API 使用wx.showActionSheet接口可以显示操作菜单&#xff0c;该接口接受Object参&#xff0c;该参属性如下&#xff1a; itemList&#xff1a;string[],数组的每一项代表一个菜单选项&#xff0c;最多支持6个选项 itemColor&#xff1a;按钮的文…

开源项目推荐:Frooodle/Stirling-PDF

简介一个本地的处理 PDF 的工具&#xff0c;界面是 Web UI&#xff0c;可以支持 Docker 部署。各种主要的 PDF 操作都可以支持。比如拆分、合并、转换格式、重新排列、添加图片、旋转、压缩等等。这个本地托管的网络应用最初完全由 ChatGPT 制作&#xff0c;后来逐渐发展&#…

2023 年中国金融级分布式数据库市场报告:TiDB 位列领导者梯队,创新能力与增长指数表现突出

近日&#xff0c;沙利文联合头豹研究院发布了中国数据库系列报告之《2023 年中国金融级分布式数据库市场报告》。 报告认为&#xff0c;金融行业对于分布式数据库信任度与认可度正在逐步提高&#xff0c;中国金融级分布式数据库市场正处于成熟落地的高增长阶段&#xff0c;行业…

Flink项目实战篇 基于Flink的城市交通监控平台(上)

系列文章目录 Flink项目实战篇 基于Flink的城市交通监控平台&#xff08;上&#xff09; Flink项目实战篇 基于Flink的城市交通监控平台&#xff08;下&#xff09; 文章目录 系列文章目录1. 项目整体介绍1.1 项目架构1.2 项目数据流1.3 项目主要模块 2. 项目数据字典2.1 卡口…

Python+OpenCV 零基础学习笔记(1):anaconda+vscode+jupyter环境配置

文章目录 前言相关链接环境配置&#xff1a;AnacondaPython配置OpenCVOpencv-contrib:Opencv扩展 Notebook:python代码笔记vscode配置配置AnacondaJupyter文件导出 前言 作为一个C# 上位机&#xff0c;我认为上位机的终点就是机器视觉运动控制。最近学了会Halcon发现机器视觉还…

云计算:OpenStack 配置二层物理网卡为三层桥的接口

目录 一、理论 1.OpenStack 二、实验 1. Linux系统修改网卡 2.OpenStack 配置二层物理网卡为三层桥的接口 一、理论 1.OpenStack &#xff08;1&#xff09;概念 OpenStack是一个开源的云计算管理平台项目&#xff0c;是一系列软件开源项目的组合。由NASA(美国国家航空…

IntelliJ IDEA Apache Dubbo,IDEA 官方插件正式发布!

作者&#xff1a;刘军 最受欢迎的 Java 集成开发环境 IntelliJ IDEA 与开源微服务框架 Apache Dubbo 社区强强合作&#xff0c;给广大微服务开发者带来了福音。与 IntelliJ IDEA 2023.2 版本一起&#xff0c;Jetbrains 官方发布了一款全新插件 - Apache Dubbo in Spring Frame…

Qt sender()函数

sender函数原型&#xff1a; QObject *sender() const; 如果在由信号激活的插槽中调用该函数&#xff0c;返回指向发送信号的对象的指针&#xff0c;否则返回0&#xff0c;该指针仅在从该对象的线程上下文调用此函数的槽执行期间有效。 主要代码如下&#xff1a; 其中运用了Q…

创建springboot项目

SpringBoot 就相当于不需要配置文件的SpringSpringMVC。 常用的框架和第三方库都已经配置好了。 maven安装配置 管理项目依赖库的 maven的安装教程网上有很多&#xff0c;这里简单记录一下。 官网下载maven后并解压。 在其目录下添加一个目录repository 然后在conf目录下…

学习笔记 k8s常用kubectl命令

k8s常用kubectl命令 pod 相关强制删除pod查看 Pod 中指定容器的日志pod 扩容 etcd 备份集群设置集群上下文配置文件切换集群 节点cordondrain pod 相关 强制删除pod pod 状态terminal了&#xff0c;需要强制删除 kubectl delete pod <pod_name> --grace-period0 --force…

uniapp原生插件 - android原生插件打包流程 ( 避坑指南一)

当时开发中安卓插件打包成功后&#xff0c;uniapp引用插件aar&#xff0c;用云打包 &#xff0c;总是提示不包含插件。原因是因为module的androidManifest.xml文件没有注册activity。 这一步 很重要&#xff0c;一定要注册。 ------------------------------------------------…

Appium+python自动化(二)- 环境搭建—下(超详解)

简介 宏哥的人品还算说得过去&#xff0c;虽然很久没有搭建环境了&#xff0c;但是换了新电脑设备&#xff0c;一气呵成&#xff0c;将android的测试开发环境已经搭建准备完毕。上一篇android测试开发环境已经准备好&#xff0c; 那么接下来就是appium的环境安装和搭建了。 嘿…

Django信号机制源码分析(观察者模式)

Django信号的实现原理本质是设计模式中的观察者模式&#xff0c;浅谈Python设计模式 -- 观察者模式&#xff0c;也可以叫做发布-订阅模式&#xff0c;信号对象维护一个订阅者列表&#xff0c;当信号被触发时&#xff0c;它会遍历订阅者&#xff0c;依次通知它们。 先来回顾一下…

Github 2023-12-28开源项目日报 Top10

根据Github Trendings的统计&#xff0c;今日(2023-12-28统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目3TypeScript项目3非开发语言项目2Java项目1HTML项目1Svelte项目1 系统设计课程 创建周期&#xf…

kubelet源码学习(二):kubelet创建Pod流程

本文基于Kubernetes v1.22.4版本进行源码学习 4、kubelet创建Pod流程 syncLoop()的主要逻辑是在syncLoopIteration()方法中实现&#xff0c;Pod创建相关代码只需要看处理configCh部分的代码 // pkg/kubelet/kubelet.go // 该方法会监听多个channel,当发现任何一个channel有数…

ArkTS基本概念装饰器

目录 ArkTS基本概念 装饰器汇总 ArkTS基本概念 ArkTS是HarmonyOS的主力应用开发语言。 它在TypeScript&#xff08;简称TS&#xff09;的基础上&#xff0c;匹配ArkUI框架&#xff0c;扩展了声明式UI、状态管理等相应的能力&#xff0c;让开发者以更简洁、更自然的方式开发跨…

17. 电话号码的字母组合中

给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 1&#xff1a; 输入&#xff1a;digits "23" 输出&#…

Python字符串指定第几个字符前后截取

概述 我会以从前和从后遇到相应值进行截取为示例来进行讲解&#xff0c;授人以鱼不如授人以渔&#xff01; 示例 截取最后一个/后的数据 如果你有一个路径字符串&#xff0c;并且想要截取路径中最后一个斜杠 (/) 后的数据&#xff0c;你可以使用 Python 的字符串操作来实现…

nodejs业务分层如何写后端接口

这里展示的是在node express 项目中的操作 &#xff0c;数据库使用的是MongoDB&#xff0c;前期关于express和MongoDB的文章可访问&#xff1a; Nodejs后端express框架 server后端接口操作&#xff1a;通过路由匹配——>调用对应的 Controller——>进行 Service调用——&…

Postman接口测试工具使用

一、前言 在前后端分离开发时&#xff0c;后端工作人员完成系统接口开发后&#xff0c;需要与前端人员对接&#xff0c;测试调试接口&#xff0c;验证接口的正确性可用性。而这要求前端开发进度和后端进度保持基本一致&#xff0c;任何一方的进度跟不上&#xff0c;都无法及…