后端技术_应用层协议设计ProtoBuf
最近更新:2024-09-23
|
字数总计:1.9k
|
阅读估时:7分钟
|
阅读量:次
- 应用层协议设计
- 消息边界
- 协议设计重点
- 范例
- IM即时通讯
- 云平台节点服务器
- ngix
- HTTP
- 序列化
- 方法
- 数据压缩
- 协议升级
- Protobuf
- protoc
- 编译.pb.cc
- 工程经验
- proto语法
- 编码原理
- 基础
- .proto数据类型
- 序列化后数据类型
- 可变长编码
- 编码规则
- 编号+类型段
- wire_type = 1 or 5
- wire_type = 0
- wire_type = 2
- 协议升级
- protobuf为什么快
应用层协议设计
消息边界
- 固定大小字节数,如每个消息100个字节(不足也要100个字节)(一般不用这个);
- 特定符号,如每个消息以特定字符\r\n来结尾;
- 固定消息头+消息体,消息头中有消息体大小,先接受固定字节数的头部,解出消息完整的长度;
- buffer前面加一个字符流头部,其中有个字段有消息总长度,收消息先判断是否有结束符,然后解析消息头,再接受解出体长度的消息体;
协议设计重点
- 消息边界
- 版本区别,版本号放哪里?
- 消息类型区分,如login,msg
范例
IM即时通讯
| 字段 |
类型 |
长度(字节) |
说明 |
| length |
unsigned int |
4 |
整个消息的长度 包括 头 + body |
| version |
unsigned short |
2 |
通信协议版本号 |
| appid |
unsigned short |
2 |
对外SDK提供服务时,用来识别不同的客户 |
| service_id |
unsigned short |
2 |
对应命令的分组类别,比如login和msg是不同的分组 |
| command_id |
unsigned short |
2 |
分组里面的子命令,比如login和login response |
| seq_num |
unsigned short |
2 |
消息序号 |
| reserve |
unsigned short |
2 |
预留字节 |
| body |
unsigned char[] |
n |
具体的协议数据 |
云平台节点服务器
| 字段 |
类型 |
长度(字节) |
说明 |
| STAG |
unsigned short |
2 |
通信协议数据包开始的标志 0xff oxfe h264 0 0 0 1 |
| version |
unsigned short |
2 |
通信协议版本号,0x01 |
| checksum |
unsigned char |
1 |
计算协议数据校验和,如果为加密数据,则计算密文校验和。校验和计算范围:协议头checksum字段后的数据,协议体全部数据。 |
| type |
unsigned char |
1 |
0表示协议体是json格式,其他值未定。设备心跳消息类型的值为0xA0。 |
| seqno |
unsigned int |
4 |
通信数据报文的序列号,应答报文序列号必须与请求报文序列号相同。 |
| Length |
unsigned int |
4 |
报文内容长度,即从该字段后报文内容长度。 |
| reserve |
unsigned int |
4 |
预留字节,设备心跳消息类型的值为devid。 |
| body |
unsigned char[] |
n |
具体的协议数据 |
ngix

HTTP

序列化
- 序列化:将客户端/服务端对象数据转换成易于网络传输的字符流or二进制流
- 反序列化:逆过程
方法
- xml(文本)
- json(文本)
- protobuf(二进制)
数据压缩
- 针对body中的文本,二进制的图片、视频和protobuf没必要压缩
- deflate
- gzip
- lzw
协议升级
- 通过版本号指明协议版本,即通过版本号辨别不同类型的协议
- 支持协议头可扩展,即在设计协议头部对的时候有一个字段用来指明头部的长度
Protobuf
- 编写proto文件
- 通过protoc工具生成对应语言的文件,如xx.pb.h和xx.pb.cc
- 引用头文件.pb.h,直接使用生成的接口
- 通过序列化和反序列化,填充body或解析body
protoc
- 将制定proto文件生成代码
- protoc -l=./ –cpp_out=./test.proto
- 将对应目录的所有proto文件生成代码
- protoc -l=./ –cpp_out=./*.proto
编译.pb.cc
- g++ -std=c++11 -o list_people list_people.cc addressbook.pb.cc -lprotobuf -lpthread
工程经验
- proto文件命名规则,尽量用”项目名.模块名.proto”
- proto命名空间,尽量用”package 项目名.模块名;”
- 引用文件,使用”import “项目名.模块名.proto””
- 多个平台一定要用同一份proto文件,避免出错
- 可以使用”option optimize_for = LITE_RUNTIME”来链接lite版本的libprotobuf
1 2 3 4 5 6 7 8 9
| sytax = "proto3"; package IM.Login; import "IM.BaseDefine.proto";
option optimize_for = LITE_RUNTIME; message IMLoginReq{ ... }
|
proto语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
|
syntax = "proto3"; package IM.Login; import "IM.BaseDefine.proto";
option optimize_for = LITE_RUNTIME;
message IMLoginReq{ string user_name = 1; string password = 2; IM.BaseDefine.UserStatType online_status = 3; IM.BaseDefine.ClientType client_type = 4; string client_version = 5; string client_xxx = 6;
oneof test_union { int32 int32_1 = 7; string string_2 =8; }
oneof payload { ArticleResponse articleResponse = 9; MusicResponse musicResponse = 10; } }
message ArticleResponse { string title = 1; string author = 2; string date = 3; } message MusicResponse { string title = 1; string author = 2; bytes data = 3; }
enum PhoneType{ PHONE_DEFAULT = 0x0; PHONE_HOME = 0x0001; PHONE_WORK = 0x0002; }
message Phone{ string number = 1; 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; }
|
编码原理
基础
.proto数据类型

序列化后数据类型
- 序列化后的二进制里,数据只有4种类型,该类型称为wire_type
- 0 Varint(变长类型 int32 int64 uint32 uint64 sint32 sint64 bool enum)
- 1 64-bit(固定64位长度 fixed64 sfixed64 double)
- 2 Length-delimited(字符等 string,bytes,embedded mssages,packed repeated field)
- 5 32-bit(固定32位长度 fixed32 sfixed32 float)
可变长编码
- 定长编码:在C++中int32类型始终占用4个字节,即时存储的是0或1依然占用4个字节,会有浪费掉的空间
- 可变长编码 Base128 Varint:(128表示用7个位来编码的意思)
- 每个字节的第一个bit用来表示该字节后续还有没有下一个字节,1表示有,0表示结束
- 采用小端序,低位将会存储在高地址
- 对于存储1:
- 定长:00000000 00000000 00000000 00000001
- 变长:00000001(第一个bit为0,表示已结束)
- 对于存储666:
- 定长:00000000 00000000 00000010 10011010
- 先以7个位进行拆分 101 0011010
- 低位放到前面 0011010 101
- 添加表示后续仍有字节的标识 10011010 00000101 ,这一位全补0代表后面没有字节了
- 变长:10011010 00000101
- 对于数据类型的选择:
- 如果含有负数建议采用sint类型,因为效率问题。
- 0xffffffff采用定长需要4个字节,而采用变长需要32/7≈5个字节,所以在<=28bit整数适合用变长,而>28且<=32bit可以考虑fixed32或sfixed32
编码规则
编号+类型段
- 编号即.proto文件中定义的编号,称为field_num,采用Base128 Varint可变长编码
- 序列化后的数据类型,称为wire_type,采用固定3bit表示
- 那么存储数据的第一个段为 field_num << 3 | wire_type
wire_type = 1 or 5
wire_type = 0
- 跟在编号+类型段后,可变长度,且采用Base128 Varint可变长编码
wire_type = 2
- 跟在编号+类型段后,添加一个长度字段,采用Base128 Varint可变长编码
- 长度字段后面跟具体数据,一般为字符串的asii码
协议升级
- 不要改变编号
- 直接新加字段即可
- 如果想废弃字段,考虑添加reserved标记,不要之间删掉
protobuf为什么快
- 根据编号去匹配,占用字节少
- 解析是二进制
- 传输是二进制,占用字节少
2024-03-05
该篇文章被 Cleofwine
归为分类:
服务端