Google的Protobuf协议分析
protobuf和thrift类似,也是一个序列化的协议实现,简称PB(下文出现的PB代表protobuf)。
Github:https://github.com/google/protobuf
上图,说明一下protobuf协议。
PB以“1-5个字节”的编号和类型开头,格式:编号左移3位和类型取或得到。
编号是什么?
编号就是 定义的proto文件中各个字段的编号。
如:
类型是什么?
类型就是 定义的proto文件中各个字段类型,使用3位表示类型,可以表示0到7,共8种类型,PB类型只用了0,1,2,3,4,5这6种类型。
详细描述参考如下表格:
类型 | 描述 | 使用于哪些类型 |
varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum | |
64-bit | fixed64, sfixed64, double | |
Length-delimited | string, bytes, embedded messages, packed repeated fields | |
Start group | groups (deprecated) | |
End group | groups (deprecated) | |
32-bit | fixed32, sfixed32, float |
看到图和表格时是不是有很多迷惑的地方?
1. 为什么编号类型,32位数值,32位负载长度数值都占用 “1-5个字节”?
2. 为什么64为的数值占用“1-10个字节”?
3. Varint是什么?
4. ZigZag是什么?
解决这些问题的关键:PB对数值进行压缩,压缩算法就是Varint,负数进行zigzag编码后再做varint编码,什么是Varint数值压缩?
为了详细的了解varint的编码,可以参考我的另一篇文章 Thrift TCompactProtocol协议分析的varint介绍部分。
看完链接中描述的varint编码和zigzag编码后,继续分析。
编写一个demo分析一下PB协议。
1. 编写proto接口文件
package demo; enum AuctionType {
FIRST_PRICE = 1;
SECOND_PRICE = 2;
FIXED_PRICE = 3;
} message VarintMsg {
required int32 argI32 = 1;
required int64 argI64 = 2;
required uint32 argUI32 = 3;
required uint64 argUI64 = 4;
required sint32 argSI32 = 5;
required sint64 argSI64 = 6;
repeated bool argBool = 7;
optional AuctionType argEnum = 8;
} message Bit64 {
required fixed64 argFixed64 = 1;
required sfixed64 argSFixed64 = 2;
required double argDouble = 3;
} message Bit32 {
required fixed32 argFixed32 = 1;
required sfixed32 argSFixed32 = 2;
required float argFloat = 3;
} message LenPayload {
repeated string argStrList = 1;
optional VarintMsg argVarintMsg = 2;
optional Bit64 argBit64 = 3;
optional Bit32 argBit32 = 4;
}
2. 编写测试代码
/*
** Copyright (C) 2014 Wang Yaofu
** All rights reserved.
**
**Description: The source file of demo.
*/ #include "demo.pb.h"
#include <string>
#include <iostream>
#include <fstream>
using namespace std; int appendFile(const string& file, const char* dataPtr, int len) {
std::ofstream ofs(file, std::ofstream::app | std::ofstream::binary);
if (ofs.is_open() && ofs.good()) {
ofs.write(dataPtr, len);
}
return len;
} int main(int argc, char *argv[]) {
demo::VarintMsg* varintMsg = new demo::VarintMsg();
varintMsg->set_argi32(0x41);
varintMsg->set_argi64(0x12345678);
varintMsg->set_argui32(0x332211);
varintMsg->set_argui64(0x998877);
varintMsg->set_argsi32(-100);
varintMsg->set_argsi64(-200);
varintMsg->add_argbool(true);
varintMsg->add_argbool(false);
varintMsg->set_argenum(demo::SECOND_PRICE); demo::Bit64* bit64 = new demo::Bit64();
bit64->set_argfixed64(0x123456);
bit64->set_argsfixed64(-100);
bit64->set_argdouble(3.1415926); demo::Bit32* bit32 = new demo::Bit32();
bit32->set_argfixed32(0x1234);
bit32->set_argsfixed32(-10);
bit32->set_argfloat(3.1415); demo::LenPayload* lenPayload = new demo::LenPayload();
lenPayload->add_argstrlist("String 1.");
lenPayload->add_argstrlist("String 2.");
lenPayload->set_allocated_argvarintmsg(varintMsg);
lenPayload->set_allocated_argbit64(bit64);
lenPayload->set_allocated_argbit32(bit32);
std::string content;
lenPayload->SerializeToString(&content);
appendFile("pb.bin", content.data(), content.length());
delete lenPayload;
return 0;
}
3. 编写Makefile
CXX = g++ -g -std=c++11
PB_HOME = ./tools/protobuf-2.6.1/inbin/
PROTOC = LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:$(PB_HOME)/lib $(PB_HOME)/bin/protoc
CXXFLAGS = -I$(PB_HOME)/include -I.
LDFLAGS = -L$(PB_HOME)/lib -lprotobuf all: demo.pb.h demo
demo.pb.h :
$(PB_HOME)/bin/protoc --cpp_out=. ./demo.proto
demo :
${CXX} ${CXXFLAGS} -o demo demo.cpp ${LDFLAGS} clean:
rm -rf demo *.pb.*
4. 编译后运行demo,得到二进制文件pb.bin
5. 按字节分析
5.1.消息message LenPayload的第一个字段分析:
repeated string argStrList = 1;
字节 0a 表示编号和类型:
编号为1,类型为2,1 << 3 | 2 = 1000 | 0010 = 1010 = 8+2 = 10 = 0a
字节 09 表示负载信息的长度为9:
字节:"53 74 72 69 6e 67 20 31 2e" 为 "String 1. ",长度正好为9.
字段argStrList是可重复的,所以紧接着的字节 0a 09表示编号类型和长度。
字节:"53 74 72 69 6e 67 20 32 2e" 为 "String 2. "。
对应代码:
lenPayload->add_argstrlist("String 1.");
lenPayload->add_argstrlist("String 2.");
5.2. 消息message LenPayload的第二个字段分析:
optional VarintMsg argVarintMsg = 2;
字节:"12 1e 08 41 10 f8 ac d1 91 01 18 91 c4 cc 01 20 f7 90 e6 04 28 c7 01 30 8f 03 38 01 38 00 40 02"
字节 12 表示编号和类型:
编号为2,类型为2,2 << 3 | 2 = 10000 | 0010 = 10010 = 16+2 = 18 = 0x12
字节 1e 表示负载信息的长度为30.
5.2.1. message VarintMsg消息分析
required int32 argI32 = 1;
varintMsg->set_argi32(0x41);
08 41
字节08 表示编号和类型:
编号为1,类型为0,1 << 3 | 0 = 1000 | 0000 = 1000 = 8 = 0x08
字节 41 表示值为 0x41.
required int64 argI64 = 2;
varintMsg->set_argi64(0x12345678);
10 f8 ac d1 91 01
字节10 表示编号和类型:
编号为2,类型为0,2 << 3 | 0 = 10000 | 0000 = 10000 = 16 = 0x10
字节 f8 ac d1 91 01二进制表示值为
1111 1000, 1010 1100, 1101 0001, 1001 0001, 0000 0001
小端转本地为 0000 0001, 001 0001, 101 0001, 010 1100, 111 1000
去掉红色的1,varint恢复为 0001 0010, 0011 0100, 0101 0110, 0111 1000 表示为16进制就是 0x12345678
required uint32 argUI32 = 3;
varintMsg->set_argui32(0x332211);
18 91 c4 cc 01
字节18 表示编号和类型:
编号为3,类型为0,3 << 3 | 0 = 11000 | 0000 = 11000 = 16 + 8 = 24 = 0x18
字节91 c4 cc 01二进制为 1001 0001, 1100 0100, 1100 1100, 0000 0001
小端转本地为 0000 0001, 100 1100, 100 0100 , 001 0001
去掉红色的1,varint恢复为 0011 0011, 0010 0010, 0001 0001 表示为16进制就是 0x332211
required uint64 argUI64 = 4;
varintMsg->set_argui64(0x998877);
20 f7 90 e6 04
字节20 表示编号和类型:
编号为4,类型为0,4 << 3 | 0 = 100000 | 0000 = 100000 = 32 = 0x20
字节 f7 90 e6 04二进制为 1111 0111, 1001 0000, 1110 0110, 0000 0100
小端转本地为 0000 0100, 110 0110, 1001 0000, 111 0111
去掉红色的1,varint恢复为 1001 1001,1000 1000, 0111 0111 表示为16进制就是 0x998877
required sint32 argSI32 = 5;
varintMsg->set_argsi32(-100);
28 c7 01
字节28 表示编号和类型:
编号为5,类型为0,5 << 3 | 0 = 101000 | 0000 = 101000 = 32 + 8 = 40 = 0x28
字节 c7 01二进制表示为 1100 0111, 0000 0001
小端转为本地为 0000 0001, 100 0111
去掉红色的1,varint恢复为 1100 0111 = 199 = -100 * -2 - 1,正好是-100做zigzag后varint压缩得到的值。
required sint64 argSI64 = 6;
varintMsg->set_argsi64(-200);
30 8f 03
字节30 表示编号和类型:
编号为6,类型为0,6 << 3 | 0 = 110000 | 0000 = 110000 = 32 + 16 = 48 = 0x30
字节 8f 03二进制表示为1000 1111, 0000 0011
小端转本地为 0000 0011, 000 1111
去掉红色的1,varint恢复为11000 1111 = 399 = -200 * -2 -1,正好是-200做zigzag后varint压缩得到的值。
repeated bool argBool = 7;
varintMsg->add_argbool(true);
38 01
字节38 表示编号和类型:
编号为7,类型为0,7 << 3 | 0 = 111000 | 0000 = 111000 = 32 + 16 + 8 = 56 = 0x38
字节 01 表示值为1, 是true.
repeated bool argBool = 7;
varintMsg->add_argbool(false);
38 00
字节38 表示编号和类型:
编号为7,类型为0,7 << 3 | 0 = 111000 | 0000 = 111000 = 32 + 16 + 8 = 56 = 0x38
字节 00 表示值为 0,是false.
optional AuctionType argEnum = 8;
varintMsg->set_argenum(demo::SECOND_PRICE);
40 02
字节40表示编号和类型:
编号为3,类型为0,8 << 3 | 0 = 1000000 | 0000 = 1000000 = 64 = 0x40
字节 02 表示值为 2,是枚举的值demo::SECOND_PRICE值为2.
5.3. 消息message LenPayload的第三个字段分析:
optional Bit64 argBit64 = 3;
“1a 1b 09 56 34 12 00 00 00 00 00 11 9c ff ff ff ff ff ff ff 19 4a d8 12 4d fb 21 09 40”
字节 1a 表示编号和类型:
编号为3,类型为2,0x1a = 3 << 3 | 2 = 11000 | 0010 = 11010 = 16+8+2 = 26 = 0x1a
字节 1b 表示负载信息的长度为27。
5.3.1. message Bit64消息分析
required fixed64 argFixed64 = 1;
bit64->set_argfixed64(0x123456);
09 56 34 12 00 00 00 00 00
字节 09 表示编号和类型:
编号为1,类型为1,0x0d = 1 << 3 | 1 = 1000 | 0001 = 1001 = 8+1 = 9 = 0x09
接着8个字节表示64位数的负载信息。
"56 34 12 00 00 00 00 00":从小端表示转成本地表示为 00 00 00 00 00 12 34 56, 表示 0x123456
required sfixed64 argSFixed64 = 2;
bit64->set_argsfixed64(-100);
11 9c ff ff ff ff ff ff ff
字节 09 表示编号和类型:
编号为2,类型为1,0x11 = 2 << 3 | 1 = 10000 | 0001 = 10001 = 16+1 = 17 = 0x11
接着8个字节表示64位数的负载信息。
"9c ff ff ff ff ff ff ff":从小端表示转成本地表示为 ff ff ff ff ff ff ff 9c, 表示 -100
required double argDouble = 3;
bit64->set_argdouble(3.1415926);
19 4a d8 12 4d fb 21 09 40
字节 19 表示编号和类型:
编号为3,类型为1,0x19 = 1 << 3 | 1 = 11000 | 0001 = 11001 = 16+8+1 = 25 = 0x19
接着8个字节表示64位数的负载信息。
"4a d8 12 4d fb 21 09 40":表示: 3.1415926
5.4. 消息message LenPayload的第四个字段分析:
optional Bit32 argBit32 = 4;
“22 0f 0d 34 12 00 00 15 f6 ff ff ff 1d 56 0e 49 40”
字节 22 表示编号和类型:
编号为4,类型为2,0x22 = 4 << 3 | 2 = 100000 | 0010 = 100010 = 32+2 = 34 = 0x22
字节 0f 表示负载信息的长度为15:
5.4.1. message Bit32消息分析
required fixed32 argFixed32 = 1;
bit32->set_argfixed32(0x1234);
0d 34 12 00 00
字节 0d 表示编号和类型:
编号为1,类型为5,0x0d = 1 << 3 | 5 = 1000 | 0101 = 1101 = 8+4+1 = 13 = 0x0d
接着4个字节表示32位数的负载信息。
"34 12 00 00":从小端表示转成本地表示为 00 00 12 34, 表示 0x1234
required sfixed32 argSFixed32 = 2;
bit32->set_argsfixed32(-10);
15 f6 ff ff ff 1d
字节 15 表示编号和类型:
编号为2,类型为5,0x15 = 1 << 3 | 5 = 10000 | 0101 = 10101 = 16+4+1 = 21 = 0x15
接着4个字节表示32位数的负载信息。
"ff ff ff 1d": 表示 -10
required float argFloat = 3;
bit32->set_argfloat(3.1415);
1d 56 0e 49 40
字节 1d 表示编号和类型:
编号为3,类型为5,0x1d = 3 << 3 | 5 = 11000 | 0101 = 11101 = 16+8+4+1 = 29 = 0x1d
接着4个字节表示32位数的负载信息。
"56 0e 49 40": 表示 3.1415
测试代码:https://github.com/gityf/utils/tree/master/pb_analysis_demo
Done.
Google的Protobuf协议分析的更多相关文章
- [转载] TLS协议分析 与 现代加密通信协议设计
https://blog.helong.info/blog/2015/09/06/tls-protocol-analysis-and-crypto-protocol-design/?from=time ...
- TLS协议分析
TLS协议分析 本文目标: 学习鉴赏TLS协议的设计,透彻理解原理和重点细节 跟进一下密码学应用领域的历史和进展 整理现代加密通信协议设计的一般思路 本文有门槛,读者需要对现代密码学有清晰而系统的理解 ...
- protobuf 协议浅析
目录 Protobuf 协议浅析 1. Protobuf 介绍 1.1 Protobuf 基本概念 1.2 Protobuf 的优点 1.3 Protobuf, JSON, XML 的区别 2. Pr ...
- python爬虫之protobuf协议介绍
前言 在你学习爬虫的知识过程中是否遇到下面的类型.如果有兴趣学习一下或者了解相关知识的,且不嫌在下才疏学浅,可以参考一下.欢迎各位网友的指正. 首先叙述一下问题的会出现的式样. 你可能会在请求参数中看 ...
- Thrift的TCompactProtocol紧凑型二进制协议分析
Thrift的紧凑型传输协议分析: 用一张图说明一下Thrift的TCompactProtocol中各个数据类型是怎么表示的. 报文格式编码: bool类型: 一个字节. 如果bool型的字段是结构体 ...
- Memcache的使用和协议分析详解
Memcache的使用和协议分析详解 作者:heiyeluren博客:http://blog.csdn.NET/heiyeshuwu时间:2006-11-12关键字:PHP Memcache Linu ...
- 物联网MQTT协议分析和开源Mosquitto部署验证
在<物联网核心协议—消息推送技术演进>一文中已向读者介绍了多种消息推送技术的情况,包括HTTP单向通信.Ajax轮询.Websocket.MQTT.CoAP等,其中MQTT协议为IBM制定 ...
- netty 对 protobuf 协议的解码与包装探究(2)
netty 默认支持protobuf 的封装与解码,如果通信双方都使用netty则没有什么障碍,但如果客户端是其它语言(C#)则需要自己仿写与netty一致的方式(解码+封装),提前是必须很了解net ...
- 蓝牙协议分析(5)_BLE广播通信相关的技术分析
1. 前言 大家都知道,相比传统蓝牙,蓝牙低功耗(BLE)最大的突破就是加大了对广播通信(Advertising)的支持和利用.关于广播通信,通过“玩转BLE(1)_Eddystone beacon” ...
随机推荐
- 用字体在网页中画Icon图标
第一步,下载.IcoMoon网站选择字体图标并下载,解压后将fonts文件夹放在工程目录下.fonts文件夹内有四种格式的字体文件: 注:由于浏览器对每种字体的支持程度不一致,要想在所有浏览器中都显示 ...
- [C]指针有什么好处?
指针的好处,需要和数组比较起来说.具体如下: 1.指针可以随意申请不连续的数据存储空间,而数组是连续的,如果数组空间没有全部占用,那么会造成浪费,比如你申请了a[10],缺只有5个数据输入,那 ...
- [No0000A5]批处理常用命令大全
1.Echo 命令打开回显或关闭请求回显功能,或显示消息.如果没有任何参数,echo 命令将显示当前回显设置.语法echo [{on|off}] [message]Sample: echo off e ...
- Windows 上如何安装Sqlite
对SQLite文明已久,却是从来没使用过,今天就来安装试用下. 一.安装 下载地址:http://www.sqlite.org/download.html 将Precompiled Binaries ...
- SQLite3源程序分析之查询处理及优化
前言 查询处理及优化是关系数据库得以流行的根本原因,也是关系数据库系统最核心的技术之一.SQLite的查询处理模块很精致,而且很容易移植到不支持SQL的存储引擎(Berkeley DB最新的版本已经将 ...
- python网络编程-TCP协议中的三次握手和四次挥手(图解)
建立TCP需要三次握手才能建立,而断开连接则需要四次握手.整个过程如下图所示: 先来看看如何建立连接的. 首先Client端发送连接请求报文,Server段接受连接后回复ACK报文,并为这次连接分配资 ...
- WATERHAMMER: A COMPLEX PHENOMENON WITH A SIMPLE SOLUTION
开启阅读模式 WATERHAMMER A COMPLEX PHENOMENON WITH A SIMPLE SOLUTION Waterhammer is an impact load that is ...
- C#执行Dos命令公用方法
private static string InvokeCmd(string cmdArgs) { string Tstr = ""; Process p = new Proces ...
- Java防止SQL注入2(通过filter过滤器功能进行拦截)
首先说明一点,这个过滤器拦截其实是不靠谱的,比如说我的一篇文章是介绍sql注入的,或者评论的内容是有关sql的,那会过滤掉:且如果每个页面都经过这个过滤器,那么效率也是非常低的. 如果是要SQL注入拦 ...
- Oracle 查询表中字段里数据是否有重复
1.查找单个字段 select 字段名,count(*) from table group by 字段名 having count(*) > 1 2.查找组合字段: SELECT TEST_NA ...