Protobuf原理详解(一)
Protocol buffer 是一种语言中立,平台无关,可扩展的序列化数据的格式,Protocol buffer 很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式
什么是 Protocol Buffer?
Protocol Buffer 是一种轻量级的数据交换格式,它提供了一种简单有效的方法来序列化结构化的数据。与 JSON 或 XML 相比,Protobuf 更加紧凑、快速,并且支持多种编程语言。
protocol buffer 诞生之初是为了解决服务器端新旧协议(高低版本)兼容性问题,名字也很体贴,“协议缓冲区”。只不过后期慢慢发展成用于传输数据
基本概念
消息定义
在 Protobuf 中,数据结构被称为“消息”。这些消息定义在一个 .proto 文件中,该文件描述了消息的字段和类型。例如,我们可以定义一个简单的 Person 消息如下:
syntax = "proto3";
message person {
int32 age = 1;
}
序列化与反序列化
一旦定义了消息结构,就可以使用 Protobuf 编译器 protoc 生成特定语言的代码。这些代码允许您轻松地序列化(写入)和反序列化(读取)数据。
编码原理
Protobuf 的核心编码是基于变种的 Base128 — Base 128 Varints(这篇文章就只讲对正整数类型的编码哦)
要讲Base128就不得不先讲讲大家都很熟悉的Base64了
base64原理
为了支持回车、空格等不可见的控制字符的传递, 可以有效地将任意长度的二进制数据转换为ASCII字符,方便在各种协议中传输和处理
- Base64有64个字符, 2^6 = 64, 所以每个Base64编码字符可以用一个6位的二进制来表示
- Base64 把3个字节变成4个可打印字符

举个例子
字符串: Hello, world!
其二进制为: 01001000 01100101 01101100 01101100 01101111 00101100 00100000 01110111 01101111 01110010 01101100 01100100 00100001
3个字节一组, 最后一个字节不足3个时,用0补齐 得到
01001000 01100101 01101100
01101100 01101111 00101100
00100000 01110111 01101111
01110010 01101100 01100100
00100001 00000000 00000000
base64是每组6位
010010 (18,S) 000110 (6,G) 010101 (21,V) 101100 (44,s)
011011 000110 111100 101100
001000 000111 011101 101111
011100 100110 110001 100100
001000 010000 000000 000000
base64结果: SGVsbG8sIHdvcmxkIQ==
Base 128 Varints
base64的问题: 最高两位总是为0, 浪费空间
使用一个或多个字节对整数进行序列化。小的数字占用更少的字节。简单来说,就是尽量只储存整数的有效位,高位的 0 尽可能抛弃。
规则
低 7 位用于储存数据,最高位(最高有效位most significant bit, msb)用来标识当前字节是否是当前整数的最后一个字节
以标志位替换掉高字节的若干个0
举个例子
1 ⇒ 00000001
300 ⇒ 00000001 00101100
7位一组: 0000010 0101100
逆序: 0101100 0000010 (规定数字的高位在后)
添加msb: 10101100 00000010
下面用GO代码实现一下
package main
import (
"bytes"
"fmt"
)
func encodeVarint(value uint64) []byte {
var buf bytes.Buffer
for value >= 0x80 {
buf.WriteByte(byte(value) | 0x80)
value >>= 7
}
buf.WriteByte(byte(value))
return buf.Bytes()
}
func decodeVarint(buf []byte) (uint64, int) {
var value uint64
var shift uint
for _, b := range buf {
value |= uint64(b&0x7F) << shift
shift += 7
if b < 0x80 {
break
}
}
return value, shift / 7
}
func main() {
testValues := []uint64{1, 123, 1024, 1000000}
for _, v := range testValues {
encoded := encodeVarint(v)
fmt.Printf("Value: %d, Encoded: %v\n", v, encoded)
decoded, _ := decodeVarint(encoded)
fmt.Printf("Decoded: %d\n", decoded)
}
}