• 【Web系列二十五】前后端使用proto+grpc实现数据传输


    目录

    写在前面

    简介

    定义

    编码

    使用过程

    环境配置

    后端

    开发环境

    pom配置

    前端

    开发环境

    安装依赖

    建立通信

    定义Proto结构

    编译proto文件

    后端

    前端

    使用proto

    后端

    前端

    参考资料


    简介

    定义

            protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。通信时所传递的信息是通过Protobuf定义的message数据结构进行打包,然后编译成二进制的码流再进行传输或者存储。

            Protocol buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。

            Protobuf 使用的时候必须写一个 IDL(Interface description language)文件,在里面定义好数据结构,只有预先定义了的数据结构,才能被序列化和反序列化。其中,序列化是将对象转换二进制数据,反序列化是将二进制数据转换成对象。

    编码

            ProtoBuf 编码格式类似于TLV 格式(Tag | Length | Value),Tag为字段唯一标识,Length为Value域的长度,Value为数据本身。其中,Tag由field_number和wire_type两个部分组成,field_number是message定义字段时指定的字段编号,wire_type是ProtoBuf 编码类型,根据这个类型选择不同的 Value 编码方案。wire_type最多可表达8种编码类型(因为是3bit的),目前已经定义了六种(Varint,64-bit,Length-delimited,Start group,End group,32-bit,其中Start group和End group已经弃用)。

            在ProtoBuf 中,Length是可选的,即有些类型的数据结构编码后格式是(Tag | Value),如Varint,64-bit和32-bit。为确定这种格式的数据边界引入了Varint编码。

    使用过程

    • 第一步,创建 .proto 文件,定义数据结构。
    • 第二步,protoc 编译 .proto 文件生成读写接口
    • 第三步,调用接口实现序列化、反序列化以及读写

    环境配置

    后端

    开发环境

            spring boot + maven

    pom配置

            在Pom文件中添加以下配置。

    1. "1.0" encoding="UTF-8"?>
    2. ...
    3. <properties>
    4. ...
    5. <grpc.version>1.6.1grpc.version>
    6. <protobuf.version>3.3.0protobuf.version>
    7. <os.detected.classifer>windows-x86_64os.detected.classifer>
    8. ...
    9. properties>
    10. ...
    11. <build>
    12. <plugins>
    13. <plugin>
    14. <groupId>org.xolstice.maven.pluginsgroupId>
    15. <artifactId>protobuf-maven-pluginartifactId>
    16. <version>0.5.0version>
    17. <configuration>
    18. <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.vlassifer}protocArtifact>
    19. <pluginId>grpc-javapluginId>
    20. <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifer}pluginArtifact>
    21. <protoSourceRoot>src/main/resources/protoprotoSourceRoot>
    22. <outputDorectory>src/main/jsvsoutputDorectory>
    23. <clearOutputDirectory>false/clearOutputDirectory>
    24. configuration>
    25. <executions>
    26. <execution>
    27. <goals>
    28. <goal>compilegoal>
    29. <goal>compile-customgoal>
    30. goals>
    31. execution>
    32. executions>
    33. <plugin>
    34. plugins>
    35. build>
    36. ...
    37. project>

    前端

    开发环境

            vue

    安装依赖

    1. npm install pbjs
    2. npm install protobufjs

            在package.json中添加一下命令,其中xxx对应每个proto文件的名称,如果有多个文件,需要分多次执行

    1. {
    2. "scripts": {
    3. "proto": "pbjs -t json-module -w commonjs -o --ts src/utils/proto/xxx.ts src/utils/proto/xxx.proto"
    4. }
    5. }

    建立通信

    定义Proto结构

            student结构

    1. syntax = "proto3";
    2. package com.test.proto;
    3. option java_multiple_diles = false;
    4. option java_package = "com.test.protobuf";
    5. option java_outer_classname = "StudentVO";
    6. message Student {
    7. int32 number = 1;
    8. string name = 2;
    9. }

             继承student的class结构

    1. syntax = "proto3";
    2. import "student.proto";
    3. pacakge com.test.proto;
    4. option java_mutiple_files = false;
    5. option java_package = "com.test.protobuf";
    6. option java_outer_classname = "ClassVO";
    7. message Class {
    8. enum Grade {
    9. One = 0;
    10. Two = 1;
    11. Three = 2;
    12. };
    13. string name = 1;
    14. Grade grade = 2;
    15. com.test.proto.Student student = 3;
    16. }

             rpc服务

    1. syntax = "proto3";
    2. import "class.proto";
    3. pacakge com.test.proto;
    4. option java_mutiple_files = false;
    5. option java_package = "com.test.protobuf";
    6. option java_outer_classname = "ClassService";
    7. service ClassService {
    8. rpc SendClassMessage(Class) return (Result) {}
    9. }
    10. message Result {
    11. enum Statu {
    12. SUCCESS = 0;
    13. FAIL = 1;
    14. }
    15. State result = 1;
    16. }

            要在.proto文件中添加注释,请使用C/ C++风格的//和/ /语法。

    1. syntax:指定Proto语法,默认使用的是proto2;
    2. import:用于导入包,类似python;
    3. package:定义包的名称
    4. option:用于java。指定输出的是java包名称以及类的名称
    5. message:数据结构
    6. enum:枚举类型
    7. repeated: 表示列表
    8. service:rpc服务

    编译proto文件

            编译前要确保前后端使用的是同样的proto文件

    后端

             添加了依赖之后可以发现Maven中会有一个Protobuf的插件,点击插件中的compile就可以编译生成java格式的文件了

    前端

            修改package.json中的文件名后,执行npm run proto即可

    使用proto

            前面已经使用编译工具生成了前后端不同语言的proto类,类中定义了proto的结构以及decode\encode方法,因此使用起来很简单,只要建立普通的前后端通信,如http\websocket\rpc等,然后使用proto类作为信息的载体即可。

            以Student为例,前后端通信接口可以类似如下方式建立(代码不完整,删减了部分信息)

    后端

            StudentController.java

    1. @Api(tags = "学生信息接口")
    2. @RestController
    3. @RequestMapping("student")
    4. @CrossOrigin
    5. public class StudentController {
    6. @Autowired
    7. private StudentService studentService;
    8. @ApiOperation("根据班级信息获取学生信息")
    9. @PostMapping(value="queryStudentData", produces="application/x-protobuf")
    10. public StudentVO.Student queryStudentData(
    11. @ApiParam(name = "number", value = "学号")
    12. @RequestParam("number") Integer number
    13. ) throws IOException {
    14. return studentService.queryStudentData(number);
    15. }
    16. }

            StudentService.java

    1. public interface StudentService {
    2. StudentVO.Student queryStudentData(Integer number);
    3. }

            StudentServiceImpl.java

    1. public StudentVO.Student queryStudentData(Integer number) {
    2. StudentVO.Student.Builder student = StudentVO.Student.newBuilder();
    3. student.setNumber(number);
    4. student.setName("student1");
    5. return student.build();
    6. }

    前端

            studentServer.ts

    1. export async function queryStudentData (name: number) {
    2. return axios.create(
    3. {
    4. timeout: 5 * 60 * 1000,
    5. baseURL: 'http://' + import.meta.env.IP + ':' + import.meta.env.PORT
    6. }
    7. ).post(
    8. '/student/queryStudentData?number=' + number
    9. )
    10. }

            student.ts

    1. import { queryStudentData } from '@/api/http/studentServer.ts'
    2. function getStudent(num: number) {
    3. let student
    4. await queryStudentData(1).then((res: any) => {
    5. let uint8 = new Uint8Array(res.data)
    6. student = enc.decode(uint8)
    7. })
    8. return student
    9. }

    参考资料

    ProtoBuf介绍(内含官网汉化文档) - 知乎 (zhihu.com)

  • 相关阅读:
    GBASE 8s 关系型数据库的特点
    【C语言小游戏--猜数字】
    公开的mqtt服务器如何获得等问题
    gorm中的关联操作详解
    Redis延迟双删-架构案例2021(三十二)
    wow-singleton文件说明
    大气科学模式:WRF-chem、AERMOD模型、WRF模式、WRFDA资料同化、机器学习气象、CMAQ、SMOKE
    systemverilog学习--- coverage(覆盖率)
    因子IC、IR信息系数和信息比率的介绍
    implementation file-视频系统
  • 原文地址:https://blog.csdn.net/Nichlson/article/details/132761747