# Protobuf 使用介绍及原理 **Repository Path**: chenjim/ProtoBuf ## Basic Information - **Project Name**: Protobuf 使用介绍及原理 - **Description**: Protobuf 使用介绍及原理 - **Primary Language**: Android - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 3 - **Created**: 2020-03-20 - **Last Updated**: 2022-10-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README @[toc] # Protobuf 使用介绍及原理 ## 1. 什么是Protobuf? - 官方开源地址 :[https://github.com/protocolbuffers/protobuf](https://github.com/protocolbuffers/protobuf) - 语法指南:[https://developers.google.com/protocol-buffers/docs/proto](https://developers.google.com/protocol-buffers/docs/proto) - 本示例测试代码地址:[https://gitee.com/chenjim/ProtoBuf](https://gitee.com/chenjim/ProtoBuf) - google开源项目。序列化数据结构的方案,通常用于编写需要数据交换或者需要存储数据的程序。 这套方案包含一种用于描述数据结构的接口描述语言(Interface Description Language)和一个生成器, 用于生成描述该数据结构的不同编程语言的源代码。 ### 1.1 优点 - 性能 - 体积小,序列化后,数据大小可缩小3-10倍 - 序列化速度快,比XML和JSON快20-100倍 - 传输速度快,因为体积小,传输起来带宽和速度会有优化 - 使用 - 使用简单,proto编译器自动进行序列化和反序列化 - 维护成本低,多平台仅需维护一套对象协议文件(.proto) - 向后兼容性(扩展性)好,不必破坏旧数据格式就可以直接对数据结构进行更新 - 加密性好,Http传输内容抓包只能看到字节 - 使用范围:跨平台、跨语言(支持[Java, Python, Objective-C, C+, Dart, Go, Ruby, and C#等](https://developers.google.com/protocol-buffers/docs/tutorials)),可扩展性好 ### 1.2 缺点 - 功能,不适合用于对基于文本的标记文档(如HTML)建模,因为文本不适合描述数据结构 - 通用性较差:json、xml已成为多种行业标准的编写工具,而Protobuf只是Google公司内部的工具 - 自解耦性差:以二进制数据流方式存储(不可读),需要通过.proto文件才能了解到数据结构 ### 1.3 应用场景 - 传输数据量大 & 网络环境不稳定 的数据存储、RPC 数据交换 的需求场景 如 即时IM (QQ、微信)的需求场景 - 在传输数据量较大的需求场景下,Protobuf比XML、Json 更小、更快、使用 & 维护更简单! - AS中就有使用protobuf,参见文件Android Studio\lib\protobuf-java-3.4.0.jar ## 2. 使用 ### 2.1 安装 protoc > protoc 是编译`*.proto`为相应语言的工具 #### 2.1.1 ubuntu 安装 protoc - 方法一 **apt-get**安装 使用 `sudo apt-get install protoc` 安装 - 方法二 下载源码编译安装 1. 去[github下载*.tar.tz压缩包](https://github.com/protocolbuffers/protobuf/releases),解压, 2. 命令行进入解压后目录,执行如下指令编译 `./configure && make ` 3. 创建文件 /etc/ld.so.conf.d/libprotobuf.conf 写入内容:**/usr/local/lib** 输入命令 `sudo ldconfig` 注:protobuf的默认安装路径是/usr/local/lib,而/usr/local/lib不在Ubuntu体系默认的 LD_LIBRARY_PATH 里,后面install找不到该lib 4. `sudo make install ` 5. `protoc --version` 验证是否安装成功 #### 2.1.2 windows安装 protoc 1. 去[官方下载windows版本](https://developers.google.com/protocol-buffers/docs/downloads)解压 2. 把里面的protoc.exe所在目录,添加到环境变量 ### 2.2 使用Android的Gradle插件编译 > protobuf-gradle-plugin官方介绍:[https://github.com/google/protobuf-gradle-plugin](https://github.com/google/protobuf-gradle-plugin) - 修改项目根目录下build.gradle dependencies下增加classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8' ``` dependencies { classpath 'com.android.tools.build:gradle:3.4.0' classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } ``` - 修改app目录下build.gradle `apply plugin: 'com.android.application'`后加上`apply plugin: 'com.google.protobuf'` - 然后加入protobuf配置(与android{}同级) ``` protobuf { protoc { artifact = 'com.google.protobuf:protoc:3.0.0' } plugins { javalite { artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0' } } generateProtoTasks { all().each { task -> task.builtins { remove java } task.plugins { javalite { } } } } } ``` - 最后dependencies下加入`implementation 'com.google.protobuf:protobuf-lite:3.+'` - 安装**protobuf support**编辑插件,新建`test.proto`文件并编辑, 内容参考[test.proto](https://gitee.com/chenjim/ProtoBuf/blob/master/ProtobufAS/app/src/main/proto/test.proto) - AS编译后生成文件在`app/build/generated/source/proto/debug/javalite`目录下, 包名同[test.proto](https://gitee.com/chenjim/ProtoBuf/blob/master/ProtobufAS/app/src/main/proto/test.proto) 中定义的java_package, 类文件名同[test.proto](https://gitee.com/chenjim/ProtoBuf/blob/master/ProtobufAS/app/src/main/proto/test.proto) 中定义的java_outer_classname - 命令行编译:`protoc -I=./ --java_out=./ test.proto`,在当前目录生成同上的文件 - [protobuf-lite-3.0.0.jar下载地址](https://jcenter.bintray.com/com/google/protobuf/protobuf-lite/) - 详细工程代码参考[https://gitee.com/chenjim/ProtoBuf/tree/master/ProtobufAS](https://gitee.com/chenjim/ProtoBuf/tree/master/ProtobufAS) ## 3. Protobuf序列化、反序列化的性能对比 参见源码[MainActivity.java](https://gitee.com/chenjim/ProtoBuf/blob/master/ProtobufAS/app/src/main/java/com/dn/protobuf/MainActivity.java) 中函数protoTest() 相对FastJson和Gson的对比参考代码同上,结果如下 ``` E/MainActivity: protobuf 序列化耗时:4 E/MainActivity: protobuf 序列化数据大小:38 E/MainActivity: protobuf 反序列化耗时:2 E/JsonTest: FastJson 序列化耗时:13 E/JsonTest: FastJson 序列化数据大小:119 E/JsonTest: FastJson 反序列化耗时:16 E/JsonTest: Gson 序列化耗时:14 E/JsonTest: Gson 序列化数据大小:119 E/JsonTest: Gson 反序列化耗时:1 ``` ## 4. Protobuf 原理 - google官方原理介绍:[https://developers.google.com/protocol-buffers/docs/encoding](https://developers.google.com/protocol-buffers/docs/encoding) ### 4.1 protobuf 数据结构 - 采用 TLV存储方式,即TAG-Length-Value(标识-长度-字段值) - 不需要分隔符就能分隔开字段,减少了分隔符的使用; - 各字段存储得非常紧凑,存储空间利用率非常高; - 若字段没有被设置字段值,那么该字段在序列化时的数据中是完全不存在的,即不需要编码 ### 4.2 protobuf 编码方式 对于不同数据类型 采用不同的 序列化方式(编码方式 & 数据存储方式) ### 4.3 TAG 编码过程 `Tag = (field_number << 3) | wire_type ` wire_type 的值参考4.2,field_number就是字段标识号,详细[如下代码](https://gitee.com/chenjim/ProtoBuf/blob/master/ProtobufAS/app/src/main/proto/DNTest.proto) ``` //wire_type=0, field_number=1 //Tag = (field_number << 3) | wire_type // TAG=8 required int32 id1 = 1; //wire_type=0, field_number=2 //Tag = (field_number << 3) | wire_type // TAG=16 required int32 id2 = 2; ``` ### 4.4 Varint 编码过程 可参考源码[CodedOutputStream.java](https://github.com/protocolbuffers/protobuf/blob/master/java/core/src/main/java/com/google/protobuf/CodedOutputStream.java) 中1724行函数 writeUInt32NoTag [点我下载查看**Varint 编码过程**原图](https://pic.chenjim.com/20200323102325.png?x-oss-process=style/blog) ### 4.5 Varint 解码过程 ### 4.6 Zigzag 编码过程 ### 4.7 总结建议 - 尽量使用多用 optional或 repeated修饰符 因为若optional 或 repeated 字段没有被设置字段值,那么该字段在序列化时的数据中是完全不存在的,即不需要进行编码 - 字段标识号(Field_Number)尽量只使用 1-15,且不要跳动使用 因为Tag里的Field_Number是需要占字节空间的。如果Field_Number>16时,Field_Number的编码就会占用2个字节,那么Tag在编码时也就会占用更多的字节;如果将字段标识号定义为连续递增的数值,将获得更好的编码和解码性能 - 若需要使用的字段值出现负数,请使用 sint32 / sint64,不要使用int32 / int64 因为采用sint32 / sint64数据类型表示负数时,会先采用Zigzag编码再采用Varint编码,从而更加有效压缩数据 - 对于repeated字段,尽量增加packed=true修饰 因为加了packed=true修饰repeated字段采用连续数据存储方式,即T - L - V - V -V方式