title: ProtoBuf3定義及數(shù)據(jù)加密和解密
author: 獨孤流
date: 2024-05-29 01:12:00 +0800
categories: [加解密]
tags: [proto] # TAG names should always be lowercase
參考:
- swift-protobuf
- protobufjs
- 深入理解Protobuf3協(xié)議原理
- 精通protobuf原理之一:為什么要使用以及如何使用
- 精通protobuf原理之二:編碼原理剖析
- 精通 protobuf 原理之三:一文徹底搞懂反射原理
- protobuf通信協(xié)議(proto2)
- protobuf教程(二)---核心編碼原理
- Protobuf的高效編碼
demo: https://github.com/h42330789/StudyProto
前言
前后端數(shù)據(jù)傳輸格式,從遠古的
xml
到了現(xiàn)在通用的json
格式,但是在IM
系統(tǒng)中,為了數(shù)據(jù)量精簡以及嚴格約定各個字段,使用的一般都是proto
一丁鹉、proto
優(yōu)缺點
優(yōu)點:
1、傳輸數(shù)據(jù)精簡,數(shù)量小
2位仁、同時前后端都使用了一份源文件生成的模型對象,proto有變更時能及時知道哪里部分有修改方椎,也方便后端往前兼容歷史版本
3聂抢、傳輸過程中不可讀,天然的多了一道加密過程
4棠众、可以直接使用枚舉值
5琳疏、不需要自己想名字定義模型,對于起名字頭疼的人尤其簡單輕松
缺點:
1、傳輸過程匯總不可讀空盼,沒法查看數(shù)據(jù)
2疮薇、由于proto
嚴格控制數(shù)據(jù)順序、類型我注,只要類型和順序修改就會解析不了按咒,需要寫相關(guān)接口的人要充分熟悉proto的特性,否則出現(xiàn)了解析出錯很難排查問題
二但骨、安裝proto生產(chǎn)swift文件的環(huán)境
1励七、使用命令行工具Terminal
使用homebrew安裝
brew install swift-protobuf
2、生成proto文件對應的swift文件
// protoc --swift_out=生成文件的文件 proto源文件地址
// 以test.proto為例
protoc --swift_out=. test.proto
三奔缠、xxx.proto文件的寫法
以test.proto
為例
syntax = "proto3";
option java_package="com.xxx.xxxx.pb";
option java_outer_classname="TestProto";
//protoc.exe -I=. --java_out=../src test.proto
// 客戶端詳情
message ClientInfo {
string token = 1; // 登錄時的授權(quán)token
string macId = 2; // 生成的唯一號
int32 version = 3; // 應用版本號
int32 language = 4; // 系統(tǒng)語言
}
// 公共的返回結(jié)果
message CommonResult {
int32 errCode = 1; // 錯誤碼
string errMsg = 2; // 錯誤內(nèi)容
string flag = 3; // 擴展字段
}
// 性別
enum Gender {
Male = 0; // 男生
Female = 1; // 女生
}
// 咨詢列表[biz/stus/students]
message StudentsReq {
ClientInfo clientInfo = 1; // 對象:客戶端信息
Gender gender = 2;// 枚舉:性別
repeated int32 gradeList = 3;// 數(shù)組:年級
string name = 4;// 字符串:名稱
int32 pageNum = 5;// 數(shù)字
int32 pageSize = 6;// 數(shù)字
}
// 學生列表
message StudentsResp {
CommonResult commonResult = 1; // 結(jié)果信息
repeated BaseStudent students = 2;// 列表
int32 count = 3; //
}
message BaseStudent {
int64 stuId = 1; // 學號
string name = 2; // 名字
string pic = 3; // 頭像
int32 age = 4; // 年齡
string desc = 5; // 簡介
Gender gender = 6;// 性別
}
使用命令行生產(chǎn)swift后的源碼test.pb.swift
如下:protoc --swift_out=. test.proto
四掠抬、Proto數(shù)據(jù)內(nèi)容的加密與解密
4.0 項目里引入proto類庫Podfile
target 'TestProto' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for TestProto
pod 'SwiftProtobuf', '~> 1.18.0'
target 'TestProtoTests' do
inherit! :search_paths
# Pods for testing
end
target 'TestProtoUITests' do
# Pods for testing
end
end
4.1 Proto結(jié)構(gòu)體變成Data
// 將proto轉(zhuǎn)換成data, 然后再將data當成request里的body發(fā)送到服務端
var clientInfo = ClientInfo()
clientInfo.language = 2
clientInfo.version = 122
clientInfo.macID = "xxxx"
clientInfo.token = "yyyyyy"
var reqMessage = StudentsReq()
reqMessage.clientInfo = clientInfo
reqMessage.gender = .male
reqMessage.grade = 2
reqMessage.pageNum = 1
reqMessage.pageSize = 30
if let postData = try? reqMessage.serializedData() {
var request = URLRequest(url: URL(string: "https://xxx.xxx/biz/stus/students")!)
request.httpBody = postData
// 發(fā)起請求
URLSession.shared.dataTask(with: request, completionHandler: {_,_,_ in
})
}
1. Varint
編碼每一個字節(jié)8位=(msb位 + 7位內(nèi)容)
原理
Varint 是一種緊湊的表示數(shù)字的方法校哎。它用一個或多個字節(jié)來表示一個數(shù)字两波,值越小的數(shù)字使用越少的字節(jié)數(shù)。這能減少用來表示數(shù)字的字節(jié)數(shù)闷哆。
Varint 中的每個字節(jié)(最后一個字節(jié)除外)都設置了最高有效位(msb
)腰奋,這一位表示下一個字節(jié)(8位)是否任然為本數(shù)字的內(nèi)容,只有當msb為0時表示本數(shù)字結(jié)束抱怔。每個字節(jié)的低 7 位用于以 7 位組的形式存儲數(shù)字的二進制補碼表示劣坊,最低有效組首位。\
最高位為1代表后面7位仍然表示數(shù)字屈留,否則為0局冰,后面7位用原碼補齊。
如果用不到 1 個字節(jié)灌危,那么最高有效位設為 0 康二,如下面這個例子,1 用一個字節(jié)就可以表示勇蝙,所以 msb 為 0.
0000 0001
如果需要多個字節(jié)表示沫勿,msb 就應該設置為 1 。例如 300浅蚪,如果用 Varint 表示的話:
1010 1100 0000 0010
編碼方式
1)將被編碼數(shù)轉(zhuǎn)換為二進制表示
2)從低位到高位按照 7位 一組進行劃分
3)將大端序轉(zhuǎn)為小端序藕帜,即以分組為單位進行首尾順序交換
因為 protobuf 使用是小端序,所以需要轉(zhuǎn)換一下
4)給每組加上最高有效位(最后一個字節(jié)高位補0惜傲,其余各字節(jié)高位補1)組成編碼后的數(shù)據(jù)。
5)最后轉(zhuǎn)成 10 進制贝攒。
[圖片上傳失敗...(image-ba6ebe-1717428886556)]
圖中對數(shù)字123456進行 varint 編碼:
1)123456 用二進制表示為1 11100010 01000000
盗誊,
2)每次從低向高取 7位 變成111 1000100 1000000
3)大端序轉(zhuǎn)為小端序,即交換字節(jié)順序變成1000000 1000100 111
4)然后加上最高有效位(即:最后一個字節(jié)高位補0,其余各字節(jié)高位補1)變成11000000 11000100 00000111
5)最后再轉(zhuǎn)成 10進制哈踱,所以經(jīng)過 varint 編碼后 123456
占用三個字節(jié)分別為192 196 7
荒适。
解碼的過程就是將字節(jié)依次取出,去掉最高有效位开镣,因為是小端排序所以先解碼的字節(jié)要放在低位刀诬,之后解碼出來的二進制位繼續(xù)放在之前已經(jīng)解碼出來的二進制的高位最后轉(zhuǎn)換為10進制數(shù)完成varint編碼的解碼過程。
wire-type | 名稱 | 說明 | 類型 |
---|---|---|---|
0 | Varint | 可變長整型 | 非ZigZag編碼類型:int32, uint32, int64, uint64, bool, emum,ZigZag編碼類型:sint32, sint64 |
1 | 64-bits | 固定8個字節(jié)大小 | fixed64, sfiexed64, double |
2 | Length-delimited | Length + Body | string, bytes, embedding message, packed repeated |
5 | 32-bits | 固定4個字節(jié)大小 | fixed32, sfixed32, float |
注:
wire_type
為3-Strart Group
邪财、4-End Group
的編碼類型官方已經(jīng)棄用陕壹,所以這里也不在介紹。
Tag實現(xiàn)的結(jié)果
[7] [6] [5] [4] [3] [2] [1] [0]
|<----- field ----->|<-- wire -->|
number type