打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
unity3d google protobuf序列化与相关改进方案细说

今天我们就来聊一聊google protobuf

上一个项目我们的服务器和客户端采用的是google protobuf进行对象与字节码的序列化和反序列化的,至于protobuf的优点,我就不多说,大家可以看官网的介绍【protobuf】,总之是一种跨语言,跨平台的,比xml更加,快速高效的序列化方式。
当时我们的客户端是用的unity3d游戏引擎,语言采用的是c#,虽然第三方有对c#的支持,但是每次生成都得花费大量的时间。于是我就手写了一个简单的protobuf的序列化解析器,大家可以点这里
下面我将要说一下protobuf的核心内容。
学过编程的童鞋都知道,在日常的编程过程中一般会遇到下面的基本的数据类型:
  1. bool
  2. int
  3. long
  4. float
  5. double
  6. string
  7. byte[]
在protobuf中将bool,int,long划为一类叫做varint,字面上来将就是int变量,因为bool就是int是否为0,long就是超大的int。
而protobuf整个的序列化都依赖于varint的序列化。
google protobuf采用的是128为基础的序列化。
To understand your simple protocol buffer encoding, you first need to understand varints. Varints are a method of serializing integers using one or more bytes. Smaller numbers take a smaller number of bytes.
Each byte in a varint, except the last byte, has the most significant bit (msb) set – this indicates that there are further bytes to come. The lower 7 bits of each byte are used to store the two’s complement representation of the number in groups of 7 bits, least significant group first.
这是官方的解释。说白了就是每7位占有一个字节,除了最低byte之外,前面的byte都以1开头作为标志位。如下:
0~127 —> 00000000 ~01111111
128 —> 1|0000000 —>10000000,00000001   (2个字节)
129 —> 1|0000001 —>10000001,00000001   (2个字节)
130 —> 1|0000010 —>10000010,00000001   (2个字节)
……
就拿官网的300的序列化和反序列来说
300 = 00000001,00101100 (2个字节)
序列化的时候
00000001,00101100 —> 00|0000010|0101100
将数据以7位划分。
00,0000010,0101100
第一份为0忽略,第二部分是高位与第三部分交换位置:
0101100,0000010
将交换位置之后的前几项的第一位加1,最后一项加0,得到结果如下:
10101100,00000010
就是两个字节
反序列化的时候从第一个字节解析起,如果第一位是1,则说明后面还有有效字节,知道遇到第一位不是1的字节结束
然后将每个字节的第一位减去,然后重新逆序生成原来的数据
10101100,00000010 —>0101100,0000010—>0000010,0101100 —>00000001,00101100
最后得到300,就是这么简单。
说完varint我们就可以说怎样子对消息体进行序列化了。
//  a message
message Message {
     required int32 aInt         = 1; // a field ,field number = 1
     required float aFloat       = 2;
     required double aDouble = 3;
     required string  aString   = 4;
     required bytes  aBytes     = 5;
}
在正式的介绍序列化之前,我们先来介绍wiretype 也就是数据类型
TypeMeaningUsed For
0Varintint32, int64, uint32, uint64, sint32, sint64, bool, enum
164-bitfixed64, sfixed64, double
2Length-delimitedstring, bytes, embedded messages, packed repeated fields
3Start groupgroups (deprecated)
4End groupgroups (deprecated)
532-bitfixed32, sfixed32, float
一般用到的是0,1,2,5,对于这4种类型,我们将其分为2类,
第一类是不需要标明实际数据长度的:0,1,5,这些类型序列化就成了fieldnumber + wiretype + data
第二类是需要标明实际数据长度的:2,这种数据序列化之后就成了 fieldNumer + wiretype + datalength + data
每个message包括很多的field,每个field都有一个标志位,称为field number
就拿Message{
aInt = 300;
aFloat =0.5f;
aDouble = 0.5d;
aString = “hello world”;
aBytes  = byte[5];
}
来说这5个field是要分别进行序列化的
第一个是个int值300 序列化之后就是10101100,00000010 两个字节,fieldNumber =1,wiretype = 0
protobuf将fieldbumber 与wiretype一同进行varint序列化,
fieldNumber <<3 | wiretype的结果进行varint
1|000 为一个字节 00001000
所以最终序列化为 00001000,10101100,00000010 3个字节
aFloat=0.5按照规则:
2<<3|101 —> 00010101(第一个字节)
后4个字节是0.5进行float — byte[]的序列化
一共5个字节
aDouble = 0.5
3<<3|001 —> 00011001(第一个字节)
后8个字节是0.5进行 double —byte[] 的序列化
一共9个字节
aString = “hello”;
4<<3 | 010 —> 00100010(第一个字节)
将”hello”进行UTF8编码 得到 byte[] data
第二部分是将 data的长度进行varint序列化比如是5 —> 101
第三部分是data所以一共是1 + 1 + 5共7字节
byte[] 的序列化与string相同。
最后将每个field序列化之后的结果累计就成了message进行序列化的结果
在进行一系列的序列化之后就会存在一些问题
  1. 负数会浪费字节,尤其是-1占有10个字节
  2. bool值会占有1个字节,这个字节是否可以省略
为了解决以上2个问题。我想出了一个解决方案,主要是对wiretype下手
因为wiretype只有3个字节所以最多可以标识8个变量
修改之后变成
0(000) — 正数的varint
1(001) — 负数的varint
2(010) —- bool的true
3(011) —- bool的false
4(100) —- bit32
5(101) —- bit64
6(110) —- Length-delimited
这样子会节约少量的字节。

转自:http://www.yxkfw.com/?p=12343

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Google Protocol Buffer 的使用和原理
Unity3D客户端和Java服务端使用Protobuf
Unity实战之Protobuf案例应用
手机网络游戏应用协议设计(一)
神奇的Google二进制编解码技术:Protobuf
MessagePack, Protocol Buffers和Thrift序列化框架原理和比较说明
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服