Protobuf编码解析
protobuf的安装
tar zxvf protobuf-cpp-3.8.0.tar.gz
cd protobuf-3.8.0/
./configure CXXFLAGS="-O2" CFLAGS="-O2"
make
sudo make install
sudo ldconfig
protoc --version
protobuf编译脚本Makefile
pb_file:IM.BaseDefine.proto IM.Login.protoprotoc --proto_path=./ --cpp_out=./ *.proto
# a.out is one exe
a.out:main.cc pb_fileg++ main.cc base.pb.cc -I $INCLUDE_PATH -L $LIB_PATH -lprotobuf -pthread
clean:rm a.out *.pb.*
proto文件的定义
//IM.BaseDefine.proto
syntax = "proto3";
package IM.BaseDefine; //服务前缀,包名,防止冲突option optimize_for = LITE_RUNTIME;enum PhoneType{PHONE_DEFAULT = 0x0; PHONE_HOME = 0x0001; // 家庭电话PHONE_WORK = 0x0002; // 工作电话
}
//IM.Login.proto
syntax = "proto3";
package IM.Login; //服务前缀,包名,防止冲突
import "IM.BaseDefine.proto"; //这里包含了别的proto文件
option optimize_for = LITE_RUNTIME; //optimize_for是文件级别的选项,Protocol Buffer定义三种优化级别SPEED/CODE_SIZE/LITE_RUNTIME
//缺省情况下是SPEED。
//SPEED: 表示生成的代码运行效率高,但是由此生成的代码编译后会占用更多的空间。
//CODE_SIZE: 和SPEED恰恰相反,代码运行效率较低,但是由此生成的代码编译后会占用更少的空间,通常用于资源有限的平台,如Mobile。
//LITE_RUNTIME: 生成的代码执行效率高,同时生成代码编译后的所占用的空间也是非常少。 这是以牺牲Protocol Buffer提供的反射功能为代价的message Phone{string number = 1;IM.BaseDefine.PhoneType phone_type = 2;
}message Book{string name = 1;float price = 2;
}message Person{string name = 1;int32 age = 2;repeated string languages = 3;Phone phone = 4;repeated Book books = 5;bool vip = 6;string address = 7;
}//使用T开头测试
message TInt32{int32 int1 = 1;
}message TString{string str1 = 1;
}
#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/wait.h>//依赖proto生成的文件进行使用
#include "IM.BaseDefine.pb.h"
#include "IM.Login.pb.h"//按结构进行数据构造,并获得序列化后的数据 并输出
bool create_encode_data(std::string &strProto); //参数传出结果//根据获取到的序列化数据,进行反序列化获取原特定结构的数据
bool decode_data_get_data(std::string &strProto); //传入参数
int main()
{std::string strProto;//构造原始数据 获取序列化的数据create_encode_data(strProto);//根据序列化的数据 反序列化后打印原始结构数据decode_data_get_data(strProto);return 0;
}//根据proto文件中的结构定义,构造数据
//最终的数据即是序列化后的数据 进行分析
//传出序列化后的数据,这里仅是测试
bool create_encode_data(std::string &strProto)
{
/************************************
message Person{string name = 1;int32 age = 2;repeated string languages = 3;Phone phone = 4;repeated Book books = 5;bool vip = 6;string address = 7;
}
************************************/IM::Login::Person person;person.set_name("my name test"); // 设置以set_为前缀person.set_age(21);//repeated字段可以有多个value值person.add_languages("C++"); // 数组addperson.add_languages("Java");//取其中的子元素进行数据构造//mutable_ 嵌套对象时使用,并且是单个对象时使用IM::Login::Phone *phone = person.mutable_phone();if(!phone){std::cout << "mutable_phone failed." << std::endl;return false;}phone->set_number("137 7777 9899"); //字符串phone->set_phone_type(IM::BaseDefine::PHONE_HOME);//add_针对 repeated多个对象使用,每次增加一个,可以增加多个//添加第一个对象IM::Login::Book *book = person.add_books();book->set_name("c++ plus");book->set_price(6.7);//添加第二个对象book = person.add_books();book->set_name("Advanced Programming in the UNIX Environment");book->set_price(16.7);person.set_vip(true);person.set_address("xxxx xxx xx");//这里就已经构成了一个person的对象//可以对该数据进行序列化,用于网络传输等业务处理uint32_t buff_size = person.ByteSize();//这里使用std::string 直接存储strProto.clear();strProto.resize(buff_size);//拷贝序列化后的内容进存储空间 实际就是写入strProto 中uint8_t * c_protobuf = (uint8_t*)strProto.c_str();if(!person.SerializeToArray(c_protobuf, buff_size)){std::cout<<"proto buff to array error"<<std::endl;return false;}//输出序列化后的数据printf("序列化后的数据:\n");for(int i=0;i <buff_size; i++){printf("%02x", c_protobuf[i]);}printf("\n");// for(int i=0;i <buff_size; i++)// {// printf("%c", c_protobuf[i]);// }// printf("\n");return true;}bool decode_data_get_data(std::string &strProto)
{//对数据进行反序列化(解析),获取原结构数据(使用)//调用接口,进行解析,获取到对象结构IM::Login::Person person;//strProto 这里使用可能有点不可靠,最好长度传进来person.ParseFromArray(strProto.c_str(), strProto.size());//根据IM::Login::Person 结构对内存进行输出printf("struct data is: \n");std::cout << " name:\t" << person.name() << std::endl;std::cout << " age:\t" << person.age() << std::endl;std::string languages;for (int i = 0; i < person.languages_size(); i++){if (i != 0){languages += ", ";}languages += person.languages(i);}std::cout << " languages:\t" << languages << std::endl;// 自定义message的嵌套并且不是设置为repeated则有has_if (person.has_phone()) {const IM::Login::Phone &phone = person.phone();std::cout << " phone number:\t" << phone.number() << ", type:\t" << phone.phone_type() << std::endl;}else{std::cout << " no phone" << std::endl;}//多个元素 依次取数据for (int i = 0; i < person.books_size(); i++){const IM::Login::Book &book = person.books(i);std::cout << " book name:\t" << book.name() << ", price:\t" << book.price() << std::endl;}std::cout << " vip:\t" << person.vip() << std::endl;std::cout << " address:\t" << person.address() << std::endl;return false;
}
/****************************************
//编译命令
g++ -o test test.cpp IM.BaseDefine.pb.cc IM.Login.pb.cc -lprotobuf -lpthread -std=c++11
//如果编译有报错,请注意环境是否有以前装过的protobuf序列化后的数据:
0a0c6d79206e616d65207465737410151a03432b2b1a044a61766122110a0d3133372037373737203938393910012a0f0a08632b2b20706c7573156666d6402a330a2c416476616e6365642050726f6772616d6d696e6720696e2074686520554e495820456e7669726f6e6d656e74159a99854130013a0b7878787820787878207878
struct data is: name: my name testage: 21languages: C++, Javaphone number: 137 7777 9899, type: 1book name: c++ plus, price: 6.7book name: Advanced Programming in the UNIX Environment, price: 16.7vip: 1address: xxxx xxx xx
1、序列化与反序列化
序列化:指将结构化的数据按一定的编码规范转成指定格式的过程;
反序列化:指将转成指定格式的数据解析成原始的结构化数据的过程;
举个例子:Person是一个表示人的对象类型,person是一个Person类型的对象,将person存到一个对应的XML文档中的过程就是一种序列化,而解析XML生成对应Person类型对象person的过程,就是一个反序列化的过程。在这里结构化数据指的就是Person类型的数据,一定的编码规范指的就是XML文档的规范。XML是一种简单的序列化方式,用XML序列化的好处是,XML的通用性比较好,另外,XML是一种文本格式,对人阅读比较友好,但是XML方式比较占空间,效率也不是很高。通常,比较高效的序列化都是采用二进制方式的;将要序列化的结构化数据,按一定的编码规范,转成为一串二进制的字节流存储下来,需要用的时候再从这串二进制的字节流中反序列化出对应的结构化的数据。
2、TLV编码格式:即Tag-Length-Value(其中Length可选)的编码格式。
每个字段都使用TLV的方式进行序列化,一个消息就可以看成是多个字段的TLV序列拼接成的一个二进制字节流。其实这种方式很像Key-Value的方式,所以Tag一般也可以看做Key。显然,这种方式组织的数据并不需要额外的分隔符来划分数据,所以序列化的效率非常高(空间效率)。
编码解析
0a0c6d79206e616d65207465737410151a03432b2b1a044a61766122110a0d3133372037373737203938393910012a0f0a08632b2b20706c7573156666d6402a330a2c416476616e6365642050726f6772616d6d696e6720696e2074686520554e495820456e7669726f6e6d656e74159a99854130013a0b787878782078787820787822240a18747970652e676f6f676c65617069732e636f6d2f4661636512080a066c766c766c76Tag 字段序号 write_type0a 0c 6d7920 6e616d 652074 657374 //"my name test" 0a 00001 01010 15 //21 10 00010 0001a 03 432b2b //"C++" 1a 00011 0101a 04 4a617661 //"Java" 22 22 00100 010Length11 0x11= 170a 0a 00001 010Length 0d 0d 31333720373737372039383939 //"137 7777 9899" 10 01 //1(enum) 10 00010 0002a 2a 00101 0100f0a 0a 00001 010 Length08 08632b2b20706c7573 //"c++ plus" 15 15 00010 1016666d6402a33 //6.7 0a 2c 41647661 6e636564 2050726f 6772616d 6d696e67 20696e20 74686520 554e4958 20456e76 69726f6e 6d656e74 //"Advanced Programming in the UNIX Environment"15 9a998541 //16.730 01 //true 0011 0 0003a 0b 78787878 20787878 207878 //"xxxx xxx xx" 0011 1 010
******************************************/
protobuf序列化规则
pb编解码关键点
Varient类型
Varient类型采用Tag + Varient编码字段值的方式,bool类型和enum类型实际上都是把二者转换为int类型进行编码,因此对于这两种类型其实效率都是比较低的。另外,Varient类型其实包含Varient和ZigZag编码格式,这两个格式共用这一种类型,只是在最后解码的时候会根据proto文件中定义不同。
Length-delimited类型
这个类型的编码结构是Tag + Length + Value
1)在消息流中每个Tag(key/键)都是varint,编码方式为:field_num << 3 | wire_type。即,Tag(key/键)由 .proto文件中字段的编号(field_num) 和 传输类型(wire_type)两部分组成。
注:Tag也是Varints编码,其后三位是传输类型(wire_type),之前的数值为是字段编号(field_num)。
注意并不是说Tag只能是一个字节,这里说了Tag也是用Varint编码,显然使用Varint编码方式几千/几万的字段序号(field_num)都是可以被表示的,
Tag的取值范围最小是1,最大是229229-1,但 19000~19999 是 protobuf 预留的,用户不能使用。
虽然 Tag 的定义范围比较大,但不同 Tag 也会对 protobuf 编码带来一些影响:
1 ~ 15:单字节编码
16 ~ 2047:双字节编码
使用频率高的变量最好设置为1 ~ 15,这样可以减少编码后的数据大小,但由于Tag一旦指定不能修改,所以为了以后扩展,也记得为未来保留一些 1 ~ 15 的 Tag
2)在对一条消息(message)进行编码的时候是把该消息中所有的key-value对序列化成二进制字节流;key和value分别采用不同的编码方式。
3)消息的二进制格式只使用消息字段的字段编号(field_num)作为Tag(key/键)的一部分,字段名和声名类型只能在解析端通过引用参考消息类型的定义(即.proto文件)才能确定。
4)解码的时候解码程序(解码器)读入二进制的字节流,解析出每一个key-value对;如果解码过程中遇到识别不出来的filed_num就直接跳过。这样的机制保证了即使该消息(message)添加了新的字段,也不会影响旧的编/解码程序正常工作。
Any 的使用
syntax = "proto3";
import "google/protobuf/any.proto";enum Type
{FACE = 0;PLATE = 1;
}message Base
{Type type = 1;int32 page_number = 2;int32 result_per_age = 3;repeated google.protobuf.Any object = 4;}message Face
{string name = 1;
}message Plate
{string email = 1;
}
Oneof
Oneof 类似union,如果你的消息中有很多可选字段,而同一个时刻最多仅有其中的一个字段被设置的话,你可以使用oneof来强化这个特性并且节约存储空间,如
message LoginReply {oneof test_oneof {string name = 3;string age = 4;}required string status = 1;required string token = 2;
}
这样,name 和 age 都是 LoginReply 的成员,但不能给他们同时设置值(设置一个oneof字段会自动清理其他的oneof字段)。
升级更改 proto 需要遵循以下原则:
不要修改任何已存在的变量的 Tag
如果你新增了变量,新生成的代码依然能解析旧的数据,但新增的变量将会变成默认值。相应的,新代码序列化的数据也能被旧的代码解析,但旧代码会自动忽略新增的变量。
废弃不用的变量用 reserved 标注
int32、 uint32、 int64、 uint64 和 bool 是相互兼容的,这意味你可以更改这些变量的类型而不会影响兼容性
sint32 和 sint64 是兼容的,但跟其他类型不兼容
string 和 bytes 可以兼容,前提是他们都是UTF-8编码的数据
fixed32 和 sfixed32 是兼容的, fixed64 和 sfixed64是兼容的
=========================================================================
int类型整数编码
125 | 0x08 7d |
128 | 0x08 8001 |
129 | 0x08 8101 |
256 | 0x08 8002 |
384 | 0x08 8003 |
500 | 0x08 f403 |
512 | 0x08 8004 |
600 | 0x08 8d804 |
640 | 0x08 8005 |
10280 | 0x08 800a |
当int变量数值小于128时,采用一个字节来表示,当int类型是128的整数倍时,采用两个字节表示
如640 0x80表示 十进制128 0x05 表示640是128的5倍,0x08 Tag
500的16进制0x01f4 考虑到大小端因素,在内存中表示为0xf401,找到距离500最近的128的整数倍384 (0xf401)|| 0x8003 ==f403 【这一段是个人基于数据做的一个推断,可能真实的情况并不会这么简单】
参考文章:
图解Protobuf编码_zxh0的博客-CSDN博客
深入 ProtoBuf - 序列化源码解析
https://www.jianshu.com/p/62f0238beec8?u_atoken=f0d250b2-2b4d-4d50-aa6e-0fc989870add&u_asession=014OqJkRTgZVDmRKgjui2OjHfs-gg20WlCNLrwHWIEbnBG5W0SGoAQdY9OsjEGXdI4X0KNBwm7Lovlpxjd_P_q4JsKWYrT3W_NKPr8w6oU7K-MVEqK30GkD-VRTHe0oewU4hmsJyv-1hubKihelhNJtGBkFo3NEHBv0PZUm6pbxQU&u_asig=050SgJC_KeCxuUih-FzGZsha1qZVuOGumC2v1OfqavL8fbCS8NOEoEUNNukAFn7eKcqNiwRCkXvHOMvX5573V9JEK4UbJhq2E92EXArJdzGVWbPLzINcHSKHcCGmuZXrJRcSwWkcHOCqOWEE9IiY5wuhwV_HR-s-OYYS36di8u9fP9JS7q8ZD7Xtz2Ly-b0kmuyAKRFSVJkkdwVUnyHAIJzZuFqVlzGtwCnbJ_xzzmpBGyZ9r_tQx_Lne1XR4XpM646vxqedUl-eZLCupCM_ImYu3h9VXwMyh6PgyDIVSG1W_sV8vyVHKRW04jdIRaUJx7GVjuvW8Mbnw36tryIj0GRqRkQH9YImbnR30NpBlE5FSSbgXBGUZYXfzWH9m2Icv-mWspDxyAEEo4kbsryBKb9Q&u_aref=KGQN893tj%2FOzl3Mng3wzpcoTHGI%3D
Protobuf编码解析相关推荐
- protobuf 编码实现解析(java)
一:protobuf编码基本数据类型 public enum FieldType {DOUBLE (JavaType.DOUBLE , WIRETYPE_FIXED64 ),FLOAT (JavaTy ...
- Protobuf数据格式解析
Protobuf数据格式解析 Protobuf是Google开源的一款类似于Json,XML数据交换格式,其内部数据是纯二进制格式,不依赖于语言和平台,具有简单,数据量小,快速等优点.目前用于序列化与 ...
- HTTP1.1中CHUNKED编码解析(转载)
HTTP1.1中CHUNKED编码解析 一般HTTP通信时,会使用Content-Length头信息性来通知用户代理(通常意义上是浏览器)服务器发送的文档内容长度,该头信息定义于HTTP1.0协议RF ...
- tcpip数据包编码解析(chunk and gzip)_space of Jialy_百度空间
tcpip数据包编码解析(chunk and gzip)_space of Jialy_百度空间 // 使用zlib库的代码#include "zlib/zlib.h"void u ...
- 音频之WAV格式编码解析
学习目标: 音频之WAV格式编码解析 学习内容: 介绍 WAV是最常见的声音文件格式之一,wav文件分为两个部分,第一个部分是wav头文件,第二个部分是PCM编码的音频数据部分.是微软公司专门为Win ...
- B编码与BT种子文件分析,以及模仿json-cpp写一个B编码解析器
B编码与BT种子文件分析,以及模仿json-cpp写一个B编码解析器 1.什么是B编码 2.B编码格式 3.种子文件结构 3.1.主文件结构 3.2.info结构 4.简单的例子了解一下种子文件和B编 ...
- 逆向分析某App其Frida、Xposed、Root检测及protobuf数据解析
1.前言 接到客户需求需要分析某海外语音社交App其房间数据和榜单数据,该app除了部分hook检测外,还有个protobuf挺有意思的,现将该项目的整个流程还原 需要的工具如下: 一个app:链接: ...
- 格力空调红外编码解析
使用红外遥控空调,就必须先了解红外遥控的原理,数据的定义等.本博客解析了空调的最基本的功能的红外编码,包括:开关,温度,定时,风速,扫风,校验码.其他的功能因为空调型号不同而差别较大,所以暂时不作解析 ...
- java chunked编码解码_HTTP协议中的CHUNKED编码解析
HTTP协议中的TRANFER-ENCODING:CHUNKED编码解析 通常情况下,Transfer-Encoding域的值应当为chunked,表明采用chunked编码方式来进行报文体的传输.c ...
最新文章
- 解决 Oralce 执行set autotrace on时的SP2-0618和SP2-0611错误
- [BZOJ1419] Red is good(期望DP)
- 使用Guava CharMatcher和Apache Commons Lang StringUtils确定字符串中字符或整数的存在
- 【手算】逆序数树形计算方法
- 程序员成长最快的环境
- [转帖]mysql ERROR 1130 解决办法
- Shell 编程基础之 Case 练习
- Android RecyclerView快速上手
- 给宝宝的固态硬盘装机教程
- 深度学习落地项目 呼叫中心系统
- html英文期刊参考文献,英文参考文献标准格式
- 网络七层及四层协议通俗详解
- 计算机毕业设计Java消防安全应急培训管理平台(源码+系统+mysql数据库+Lw文档)
- 优化poi 导入导出excel,不造成电脑卡死问题
- HDR关键技术:色度学,颜色空间及转换
- html怎么使两张照片重叠,怎样把两张图片叠加在一起?
- yum 源没有php7.0,yum安装最新版php7的操作方法
- 技术领导力:作为技术团队领导经常为人所忽略的技能和职责
- Yii框架里的一些zii用法
- 手机视频连接计算机显示器播放,教您使用手机/平板电脑直接在计算机上播放视频...