protobuf使用及原理


title: ProtoBuf3定义及数据加密和解密
author: 独孤流
date: 2024-05-29 01:12:00 +0800
categories: [加解密]
tags: [proto] # TAG names should always be lowercase


参考:

demo: https://github.com/h42330789/StudyProto

前言

前后端数据传输格式,从远古的xml到了现在通用的json格式,但是在IM系统中,为了数据量精简以及严格约定各个字段,使用的一般都是proto

一、proto优缺点

优点:
1、传输数据精简,数量小
2、同时前后端都使用了一份源文件生成的模型对象,proto有变更时能及时知道哪里部分有修改,也方便后端往前兼容历史版本
3、传输过程中不可读,天然的多了一道加密过程
4、可以直接使用枚举值
5、不需要自己想名字定义模型,对于起名字头疼的人尤其简单轻松
缺点:
1、传输过程汇总不可读,没法查看数据
2、由于proto严格控制数据顺序、类型,只要类型和顺序修改就会解析不了,需要写相关接口的人要充分熟悉proto的特性,否则出现了解析出错很难排查问题

二、安装proto生产swift文件的环境

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; // 登录时的授权token
    string     macId      = 2; // 生成的唯一号
    int32      version         = 3; // 应用版本号
    int32      language        = 4; // 系统语言
}

// 公共的返回结果
message CommonResult {
    int32   errCode       = 1; // 错误码
    string  errMsg        = 2; // 错误内容
    string  flag          = 3; // 扩展字段
}

// 性别
enum Gender {
    Male  = 0; // 男生
    Female   = 1; // 女生
}

// 咨询列表[biz/stus/students]
message StudentsReq {
    ClientInfo     clientInfo     = 1; // 对象:客户端信息
    Gender   gender  = 2;// 枚举:性别
    repeated  int32   gradeList  = 3;// 数组:年级
    string   name  = 4;// 字符串:名称
    int32 pageNum  = 5;// 数字
    int32 pageSize = 6;// 数字
}

// 学生列表
message StudentsResp {
    CommonResult   commonResult   = 1; // 结果信息
    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;// 性别
}

使用命令行生产swift后的源码test.pb.swift如下:protoc --swift_out=. test.proto

四、Proto数据内容的加密与解密

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结构体变成Data

// 将proto转换成data, 然后再将data当成request里的body发送到服务端
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
    // 发起请求
    URLSession.shared.dataTask(with: request, completionHandler: {_,_,_ in
        
    })
}

1. Varint 编码每一个字节8位=(msb位 + 7位内容)

原理
Varint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。

Varint 中的每个字节(最后一个字节除外)都设置了最高有效位(msb),这一位表示下一个字节(8位)是否任然为本数字的内容,只有当msb为0时表示本数字结束。每个字节的低 7 位用于以 7 位组的形式存储数字的二进制补码表示,最低有效组首位。\

最高位为1代表后面7位仍然表示数字,否则为0,后面7位用原码补齐。

如果用不到 1 个字节,那么最高有效位设为 0 ,如下面这个例子,1 用一个字节就可以表示,所以 msb 为 0.

0000 0001

如果需要多个字节表示,msb 就应该设置为 1 。例如 300,如果用 Varint 表示的话:

1010 1100 0000 0010

编码方式

1)将被编码数转换为二进制表示

2)从低位到高位按照 7位 一组进行划分

3)将大端序转为小端序,即以分组为单位进行首尾顺序交换

因为 protobuf 使用是小端序,所以需要转换一下
4)给每组加上最高有效位(最后一个字节高位补0,其余各字节高位补1)组成编码后的数据。

5)最后转成 10 进制。

[图片上传失败...(image-ba6ebe-1717428886556)]

图中对数字123456进行 varint 编码:

1)123456 用二进制表示为1 11100010 01000000
2)每次从低向高取 7位 变成111 1000100 1000000
3)大端序转为小端序,即交换字节顺序变成1000000 1000100 111
4)然后加上最高有效位(即:最后一个字节高位补0,其余各字节高位补1)变成11000000 11000100 00000111
5)最后再转成 10进制,所以经过 varint 编码后 123456 占用三个字节分别为192 196 7
解码的过程就是将字节依次取出,去掉最高有效位,因为是小端排序所以先解码的字节要放在低位,之后解码出来的二进制位继续放在之前已经解码出来的二进制的高位最后转换为10进制数完成varint编码的解码过程。

wire-type 名称 说明 类型
0 Varint 可变长整型 非ZigZag编码类型:int32, uint32, int64, uint64, bool, emum,ZigZag编码类型:sint32, sint64
1 64-bits 固定8个字节大小 fixed64, sfiexed64, double
2 Length-delimited Length + Body string, bytes, embedding message, packed repeated
5 32-bits 固定4个字节大小 fixed32, sfixed32, float

注:wire_type3-Strart Group4-End Group 的编码类型官方已经弃用,所以这里也不在介绍。

Tag实现的结果

[7] [6] [5] [4] [3] [2]  [1]  [0]
|<----- field ----->|<-- wire -->|
        number           type 
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容