GRPC使用之ProtoBuf

news2024/11/25 18:52:12

1. 入门指导

1. 基本定义

Protocol Buffers提供一种跨语言的结构化数据的序列化能力,类似于JSON,不过更小、更快,除此以外它还能用用接口定义(IDL interface define language),通protoc编译Protocol Buffer定义文件,生成结构化类,以及服务调用的客户端和服务端。

1. person.proto

我们来看一个极简的例子,好让自己有一个直观的感受,假设我们有一个person.proto文件,内容如下:

syntax = "proto3";

option java_multiple_files = true;
option java_package = "org.keyniu.grpc.proto";

message Person {
  optional string name = 1;
  optional int32 id = 2;
  optional string email = 3;
}
2. Person.java

通过protoc生成的Person.java类大概是这样的

// Generated by the protocol buffer compiler.  DO NOT EDIT!
// source: person.proto

// Protobuf Java Version: 3.25.1
package org.keyniu.grpc.proto;

/**
 * Protobuf type {@code Person}
 */
public final class Person extends
    com.google.protobuf.GeneratedMessageV3 implements
    // @@protoc_insertion_point(message_implements:Person)
    PersonOrBuilder {
private static final long serialVersionUID = 0L;
  // Use Person.newBuilder() to construct.
  private Person(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
    super(builder);
  }
  private Person() {
    name_ = "";
    email_ = "";
  }
... // 后续的省略
3. 核心用例

我们看一下Person类的核心用法

// 创建对象
Person p = Person.newBuilder().setName("randy").setId(1).setEmail("randy@gmail.com").build();
// 序列化
byte[] serialized = p.toByteArray();
// 反序列化
Person p2 = Person.parseFrom(serialized);
System.out.println(p2);
2. 适用场景

Protocol Buffers提供了结构化数据的序列化/反序列化能力,对领域对象的修改能够兼容历史版本,官方推荐适用于小规模数据(MB级),包括网络传输、数据存储。

不适用的场景包括

  1. 不支持流式解析,待解析的数据要一次性加载进byte数组,然后解析,不能读取部分内容就交由Protocol Buffers解析
  2. 不支持二进制比较,不同语言/平台的序列化后的二进制可能是不同的,要反序列化后才能比较两个对象是否相同
  3. 不支持非面向对象的语言

2. 数据类型

我们先来回顾一下person.proto的定义,这个定义的核心是Person前面的message

message Person {
  optional string name = 1; // label(optional)、字段类型(string)、字段名(name)、字段Id(1)
  optional int32 id = 2;
  optional string email = 3;
}
1. label
label说明举例
optional字段是否可选,允许不设置值,proto3中字段默认optional,对应proto2中的requiredoptional string name = 1;
repeated可以有0或多个值,保留写入顺序repeated string name = 1;
map对应Java里的Mapmap<int32, string> idToName = 2;
字段是否存在,被称为implicit field presence,如果字段未设置值,序列化
oneof一组关联字段,只保留一个值,设置两个字段时,会把第一个清空

来看一个oneof的实例,一个Product对象,它可以参加一种促销(抵用券或打折),但不能同时参加,可以这样定义product.proto

syntax = "proto3";
option java_multiple_files = true;
option java_package = "org.keyniu.grpc.proto";

message Product {
  optional string name = 1;
  oneof promotion {
    string coupon = 2;
    string discount = 3;
  }
}

我们来看看生成的Product类,Product类自动生成了一个Product.PromotionCase类,我们可以通过它判断当前Product参加那类促销

Product prod = Product.newBuilder().setName("Mate60Pro").setCoupon("满10减3").build();
System.out.print(prod.toString());
switch (prod.getPromotionCase()) {
    case DISCOUNT:
        System.out.println(prod.getDiscount());
        break;
    case COUPON:
        System.out.println(prod.getCoupon());
        break;
}

输出如下
在这里插入图片描述

如果我们给Product同时设置Coupon和Discount,代码如下:

prod = Product.newBuilder().setName("Mate60Pro").setCoupon("满100减1").setDiscount("7折").build();
System.out.println(prod.toString());

输出如下
在这里插入图片描述

2. 字段类型

类型分为内置基本类型和自己通过message(enum)定义的类型,我们先来看看基本类型。 proto3的内置基本类型,包括整数、浮点数、布尔型、字符串以及字节数组

1. 基本类型
Proto类型对应Java类型说明默认值
doubledouble0
floatfloat0
int32int变长编码,对负数的编码效率较低,如果有负数建议使用sint320
int64long变长编码,对负数的编码效率较低,如果有负数建议使用sint640
uint32int变长编码,相当于unsigned int320
uint64long变长编码,相当于unsigned int640
sint32int变长编码,对负数的编码效率较高0
sint64long变长编码,对负数的编码效率较高0
fixed32int定长编码,总是使用4 Byte,占空间多,但编码效率高,相当于unsigned int320
sfixed32int定长编码,总是使用4 Byte,相当于signed int320
fixed64long定长编码,总是使用8 Byte,占空间多,但编码效率高,相当于unsigned int640
sfixed64long定长编码,总是使用8 Byte,相当于signed int640
boolboolean布尔值false
stringString字符串空字符串
bytesByteString字节序列,适用于存储任何数据,比如图片的byte数字空字节数组
2. 自定义类型

除此以外,proto允许用户自己通过message、enum定义自己的类型,比如之前提到的Person,我们再看一下示例

message Person {
  optional string name = 1;
  optional int32 id = 2;
  optional string email = 3;
}
3. 自定义枚举

enum关键字和Java的枚举基本一致,假设我们要定义一个性别(Gender)的枚举,可以用下面的语句定义

enum Gender {
  MALE = 0;
  FEMALE = 1;
}

要特别注意的是枚举字段的定义后面的字段id要从0开始。此外enum还有一个Java没有有的特性,枚举类型可以指定别名,比如将MAN作为MALE的别名可以这么写

enum Gender {
  option allow_alias = true;
  MALE = 0;
  MAN = 0;
  FEMALE = 1;
  WOMEN = 1;
}
4. 跨文件引用

如果通过message、enum定义的类型都在同一个文件中,可以直接相互引用,如果是在两个proto文件中,需要手动import,比如这样

import "myproject/gender.proto";
3. 字段ID

官方叫做Assigned Field Number,在person.proto中name字段的id就是1,id在同一个类型内部必须唯一,取值范围[1,5亿],一般从1开始递增,当然数字越大,消耗的储空间越大(类似UTF-8编码)。19000~19999是预留给内部使用的。

optional string name = 1;

所以这个字段标识不能修改,也不能重复,修改导致之前序列化的数据无法解析,重复导致字段混乱。

4. 预留字段

告诉Protocol Buffer预留字段Id ,2、9、10、11、15,预留字段名称foo、bar

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}
5. 对象引用

某些场景下我们可能不确定持有的数据类型,比如Object,proto也提供了这样的支持

import "google/protobuf/any.proto";

message Handler {
  string message = 1;
  repeated google.protobuf.Any target = 2;
}

通过生成对象的pack、unpack方法来访问target持有的引用

class Any {
  // Packs the given message into an Any using the default type URL
  // prefix “type.googleapis.com”.
  public static Any pack(Message message);
  // Packs the given message into an Any using the given type URL
  public static Any pack(Message message, String typeUrlPrefix);
  // Checks whether this Any message’s payload is the given type.
  public <T extends Message> boolean is(class<T> clazz);
  // Unpacks Any into the given message type. Throws exception if
  // the type doesn’t match or parsing the payload has failed.
  public <T extends Message> T unpack(class<T> clazz) throws InvalidProtocolBufferException;
}

3. 服务定义

proto3支持4中类型的服务定义,通过service关键字类定义服务的接口,比如下面示例中的Greeter服务,定义了4个方法,分别对应4种类型的调用

syntax = "proto3";

option java_multiple_files = true;
option java_package = "org.keyniu.grpc.generate";

service Greeter {
  rpc sayHello (HelloRequest) returns (HelloReply) {}
  rpc sayHelloClientStream (stream HelloRequest) returns (HelloReply) {}
  rpc sayHelloServerStream (HelloRequest) returns (stream HelloReply) {}
  rpc sayHelloBiStream (stream HelloRequest) returns (stream HelloReply) {}
}
1. 基本调用

基本调用,处理入参HelloRequest,生成响应HelloReply,在Java中怎么实现可以参考[[Helloworld#2. 实现Server]]。

rpc sayHello (HelloRequest) returns (HelloReply) {}
2. Client端Streaming

Server端Streaming,指客户端可能提交多个参数,最后响应一个结果

rpc sayHelloClientStream (stream HelloRequest) returns (HelloReply) {}
3. Server端Streaming

Server端Streaming,指客户端提供一个参数,服务端可能会有多个响应

rpc sayHelloServerStream (HelloRequest) returns (stream HelloReply) {}
4. 双向Streaming

双向Streaming,指客户端可以提交多个参数,服务端也可以有多个响应

rpc sayHelloBiStream (stream HelloRequest) returns (stream HelloReply) {}

4. JSON互操作

我们可能需要让ProtoBuff和JSON交互,ProtoBuff也为我们考虑到了这个问题,在Java中,可以使用protobuf-java-util实现这个能力

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java-util</artifactId>
    <version>3.x.x</version> <!-- 使用你的protobuf版本 -->
</dependency>
1. 转JSON
import com.google.protobuf.util.JsonFormat;
import your.package.YourProtoMessage; // 替换为你的protobuf消息类型

public class Main {
    public static void main(String[] args) throws Exception {
        YourProtoMessage message = YourProtoMessage.newBuilder() // 构建你的protobuf消息
            .setField1("value1") // 设置字段
            .setField2(123)       // 设置字段
            .build();
        JsonFormat.Printer printer = JsonFormat.printer();
        String jsonString = printer.print(message);
        System.out.println(jsonString);
    }
}
2. 解析JSON
import com.google.protobuf.util.JsonFormat;
import your.package.YourProtoMessage; // 替换为你的protobuf消息类型

public class Main {
    public static void main(String[] args) throws Exception {
        String jsonString = "{\"field1\":\"value1\",\"field2\":123}";
        JsonFormat.Parser parser = JsonFormat.parser();
        YourProtoMessage message = parser.merge(jsonString, YourProtoMessage.newBuilder()).build();
        System.out.println(message.getField1()); // 输出: value1
        System.out.println(message.getField2()); // 输出: 123
    }
}

5. 配置选项

选项示例说明
option java_packageoption java_package = “com.example.foo”生成Java类的包名
java_outer_classnameoption java_outer_classname = “Person”;生成Java类外部包装类名称
java_multiple_filesoption java_multiple_files = true;一个proto文件message、service生成多.java文件
optimize_foroption optimize_for = CODE_SIZE;可选值SPEED、CODE_SIZE、LITE_RUNTIME
SPEED: 追求执行速度,生成序列化/反序列化等代码
CODE_SIZE: 追求代码体积,通过反射实现序列化
LITE_RUNTIME: 类似SPEED,但省略descriptor和reflection代码,依赖libprotobuf-lite

A. 参考资料

  1. https://protobuf.dev/programming-guides/proto3/

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

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

相关文章

拆分Transformer注意力,韩国团队让大模型解码提速20倍|大模型AI应用开始小规模稳步爆发|周伯文:大模型也有幻觉,全球AI创新指数公布

拆分Transformer注意力&#xff0c;韩国团队让大模型解码提速20倍AI正在颠覆AI上市不到两年&#xff0c;蜗牛游戏可能要退市了&#xff1f;世界人工智能大会结束了&#xff0c;百花齐放&#xff0c;但也群魔乱舞“串联OLED”被苹果带火了&#xff0c;比OLED强在哪里&#xff1f…

赚钱小思路,送给没有背景的辛辛苦苦努力的我们!

我是一个没有背景的普通人&#xff0c;主要靠勤奋和一股钻劲&#xff0c;这十几年来我的日常作息铁打不变&#xff0c;除了睡觉&#xff0c;不是在搞钱&#xff0c;就是在琢磨怎么搞钱。 ​ 可以说打拼了十几年&#xff0c;各种小生意都做过&#xff0c;以前一直是很乐观的&…

SSM养老院管理系统-计算机毕业设计源码02221

摘要 本篇论文旨在设计和实现一个基于SSM的养老院管理系统&#xff0c;旨在提供高效、便捷的养老院管理服务。该系统将包括老人档案信息管理、护工人员管理、房间信息管理、费用管理等功能模块&#xff0c;以满足养老院管理者和居民的不同需求。 通过引入SSM框架&#x…

动手学深度学习(Pytorch版)代码实践 -循环神经网络-54循环神经网络概述

54循环神经网络概述 1.潜变量自回归模型 使用潜变量h_t总结过去信息 2.循环神经网络概述 ​ 循环神经网络&#xff08;recurrent neural network&#xff0c;简称RNN&#xff09;源自于1982年由Saratha Sathasivam 提出的霍普菲尔德网络。循环神经网络&#xff0c;是指在全…

批量爬取B站网络视频信息

使用XPath爬取B站视频链接等相关信息 分析B站html框架获取内容完整代码 对于B站&#xff0c;目前网上的爬虫大多都是使用通过解析服务器的响应来爬取想要的内容&#xff0c;下面我们通过使用XPath来爬取B站上一些想要的信息 此次任务我们需要对B站搜索到的关键字&#xff0c;并…

Linux系统安装软件包的方法rpm和yum详解

起因&#xff1a; 本篇文章是记录学习Centos7的历程 关于rpm 常见命令 1&#xff09;查看已经安装的软件包 rpm -q 软件包名 2&#xff09;查看文件的相关信息 rpm -qi 软件包名 3&#xff09;查看软件包的依赖关系 就是说要想安装这个软件包&#xff0c;就必须把一些前…

记录一次ffmpeg手动编译出现的问题

前言部分 使用环境: ubuntu 22.04 最近手动编译了一次的ffmpeg&#xff08;参考博客ffmpeg学习&#xff1a;ubuntu下编译ffmpeg(全网最懒的编译脚本)&#xff09;&#xff0c;但是过程出现了一些问题&#xff0c;因此在此记录一下&#xff0c;若有疑问&#xff0c;欢迎讨论~。 …

15集终于编译成功了-了个球!编译TFLite Micro语音识别工程-《MCU嵌入式AI开发笔记》

15集终于编译成功了-个球&#xff01;编译TFLite Micro语音识别工程-《MCU嵌入式AI开发笔记》 还是参考这个官方文档&#xff1a; https://codelabs.developers.google.cn/codelabs/sparkfun-tensorflow#2 全是干货&#xff01; 这里面提到的这个Micro工程已经移开了&#xff1…

Overleaf :LaTeX协作神器!【送源码】

Overleaf 是一个广受欢迎的在线 LaTeX 编辑器&#xff0c;专为学术写作和文档排版设计。它以其协作功能和用户友好的界面而闻名&#xff0c;使得 LaTeX 编辑变得更加容易和直观。 软件介绍 Overleaf 提供了一个基于云的 LaTeX 编辑环境&#xff0c;支持实时协作&#xff0c;使得…

哲讯SAP知识分享:SAP资产模块常用事务代码清单

在当今日益复杂的商业环境中&#xff0c;企业对于资产管理的需求日益增强。SAP作为全球领先的企业管理软件提供商&#xff0c;其资产模块&#xff08;AM&#xff09;以其高效、灵活的特性&#xff0c;为企业提供了全面的资产管理解决方案。本文将对SAP资产事务类型进行详细介绍…

算法的空间复杂度(C语言)

1.空间复杂度的定义 算法在临时占用储存空间大小的量度&#xff08;就是完成这个算法所额外开辟的空间&#xff09;&#xff0c;空间复杂度也使用大O渐进表示法来表示 注&#xff1a; 函数在运行时所需要的栈空间(储存参数&#xff0c;局部变量&#xff0c;一些寄存器信息等)…

MySQL第三天作业

一、在数据库中创建一个表student&#xff0c;用于存储学生信息 CREATE TABLE student( id INT PRIMARY KEY, name VARCHAR(20) NOT NULL, grade FLOAT ); 1、向student表中添加一条新记录 记录中id字段的值为1&#xff0c;name字段的值为"monkey"…

STM32第十六课:WiFi模块的配置及应用

文章目录 需求一、WiFi模块概要二、配置流程1.配置通信串口&#xff0c;引脚和中断2.AT指令3.发送逻辑编写 三、需求实现代码总结 需求 完成WiFi模块的配置,使其最终能和服务器相互发送消息。 一、WiFi模块概要 本次使用的WiFi模块为ESP-12F模块&#xff08;安信可&#xf…

字符串——string类的常用接口

一、string类对象的常见构造 二、string类对象的容量操作 三、string类对象的访问及遍历操作 四、string类对象的修改操作 一、string类对象的常见构造 1.string() ——构造空的string类对象&#xff0c;也就是空字符串 2.string(const char* s) ——用字符串来初始化stri…

Win10如何设置远程桌面?

远程桌面介绍 远程桌面是一款Windows提供的远程工具&#xff0c;旨在连接同一局域网内的两台计算机。如果您掌握被控端电脑的IP地址&#xff0c;便可直接连接到这台已启用远程桌面的计算机&#xff0c;通过远程桌面进行文件传输或提供远程技术支持。 在同一家公司内&#xff0…

JVM专题之垃圾收集器

JVM参数 3.1.1 标准参数 -version -help -server -cp 3.1.2 -X参数 非标准参数,也就是在JDK各个版本中可能会变动 ``` -Xint 解释执行 -Xcomp 第一次使用就编译成本地代码 -Xmixed 混合模式,JVM自己来决定 3.1.3 -XX参数 > 使用得最多的参数类型 > > 非…

十一、作业

1.从大到小输出 写代码将三个整数数按从大到小输出。 void Swap(int* px, int* py) {int tmp *px;*px *py;*py tmp;} int main() {int a 0;int b 0;int c 0;scanf("%d %d %d", &a, &b, &c);int n 0;if (a<b){Swap(&a, &b);}if (a &l…

vscode配置latex环境制作beamer ppt

vscode配置latex环境制作beamer ppt 文章目录 vscode配置latex环境制作beamer ppt1. 安装Tex Live2. 安装vscode插件3. 测试Reference 写在前面&#xff0c;笔者之前一直使用overleaf来制作beamerppt&#xff0c;但是免费版本会限制编译时间上限&#xff0c;故在本地配置了late…

高级计算机体系结构--期末真题及题型总结

2024 年春季学期期末考题回顾一、名词解释二、简答题2007 年简答题2008 年简答题简答题答案 三、分析题1. MESI 和 Dragon 协议计算给定内存存取序列所需的时钟周期2007年第一题及参考答案例题及解答 2. 顺序一致性存储模型&#xff0c;判断进程的合法输出2007年第二题及参考答…

苍穹外卖 ...待更新

苍穹外卖 1、 阿里云OSS2、菜品分类查询 1、 阿里云OSS 工具类 package com.sky.utils;import com.aliyun.oss.ClientException; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.OSSException; import lombok.AllArgsConstructor…