Protocol Buffer(简称Protobuf) 是 Google 公司内部提供的数据序列化和反序列化标准,与 JSON 和 XML 格式类似,同样大小的对象,相比 XML 和 JSON 格式, Protobuf 序列化后所占用的空间最小。
Protocol Buffers 是一种轻便高效的结构化数据存储格式,可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
protobuf-net是用于.NET代码的基于契约的序列化程序,它以Google设计的“protocol buffers”序列化格式写入数据,适用于大多数编写标准类型并可以使用属性的.NET语言。
protobuf-net可通过NuGet安装程序包,也可直接访问github下载源码:https://github.com/protobuf-net/protobuf-net 。
这里只是简单介绍一下ProtoBuf的编码结构,然后通过一个简单的序列化示例熟悉ProtoBuf的大致编码过程,具体编码规则参考ProtoBuf官网:https://developers.google.cn/protocol-buffers
TLV (Tag - Length - Value)格式:Tag 作为该字段的唯一标识,Length 代表 Value 数据域的长度,最后的 Value 便是数据本身。
ProtoBuf 编码采用类似TLV的结构,其编码结构可见下图:
注:其中的 Start group 和 End group 两种类型已被遗弃。
一个 message 编码将由一个个的 field 组成,每个 field 根据类型将有如下两种格式:
Tag - Length - Value:编码类型表中 Type = 2 即 Length-delimited 编码类型将使用这种结构,
Tag - Value:编码类型表中 Varint、64-bit、32-bit 使用这种结构。
Tag 由字段编号 field_number 和 编码类型 wire_type 组成,Tag 整体采用 Varints 编码,wire_type可用的类型如下:
Varints 编码:在每个字节开头的 bit 设置了 msb(most significant bit ),标识是否需要继续读取下一个字节,存储数字对应的二进制补码,补码的低位排在前面,类似小端模式。
ZigZag 编码:有符号整数映射到无符号整数,然后再使用 Varints 编码,sint32、sint64 将采用 ZigZag 编码(编码结构依然为 Tag - Value)。
[ProtoContract]
class Person
{
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public string Name { get; set; }
[ProtoMember(3)]
public Address Address { get; set; }
}
[ProtoContract]
class Address
{
[ProtoMember(1)]
public string Line1 { get; set; }
[ProtoMember(2)]
public string Line2 { get; set; }
}
实例化并赋值:
var person = new Person
{
Id = 12345,
Name = "Fred",
Address = new Address
{
Line1 = "Flat 1",
Line2 = "The Meadows"
}
};
序列化后的结果:
//十六进制
08-B9-60-12-04-
46-72-65-64-1A-
15-0A-06-46-6C-
61-74-20-31-12-
0B-54-68-65-20-
4D-65-61-64-6F-
77-73
//二进制
00001000-10111001-01100000-00010010-00000100-
01000110-01110010-01100101-01100100-00011010-
00010101-00001010-00000110-01000110-01101100-
01100001-01110100-00100000-00110001-00010010-
00001011-01010100-01101000-01100101-00100000-
01001101-01100101-01100001-01100100-01101111-
01110111-01110011
下面是一个ProtoBuf-Net的扩展方法类,提供了字符串、字节数组、二进制文件与对象实例之间的互相转换方法,代码如下:
using System;
using System.IO;
/*
* 博客园首发 https://www.cnblogs.com/timefiles/
* 创建时间:2021-04-10
*/
///
/// ProtoBuf-Net扩展方法类
///
public static class ProtoBufExtension
{
///
/// 将对象实例序列化为字符串(Base64编码格式)——ProtoBuf
///
/// 对象类型
/// 对象实例
/// 字符串(Base64编码格式)
public static string SerializeToString_PB(this T obj)
{
using (MemoryStream ms = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(ms, obj);
return Convert.ToBase64String(ms.GetBuffer(), 0, (int)ms.Length);
}
}
///
/// 将字符串(Base64编码格式)反序列化为对象实例——ProtoBuf
///
/// 对象类型
/// 字符串(Base64编码格式)
/// 对象实例
public static T DeserializeFromString_PB(this string txt)
{
byte[] arr = Convert.FromBase64String(txt);
using (MemoryStream ms = new MemoryStream(arr))
return ProtoBuf.Serializer.Deserialize(ms);
}
///
/// 将对象实例序列化为字节数组——ProtoBuf
///
/// 对象类型
/// 对象实例
/// 字节数组
public static byte[] SerializeToByteAry_PB(this T obj)
{
using (MemoryStream ms = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(ms, obj);
return ms.ToArray();
}
}
///
/// 将字节数组反序列化为对象实例——ProtoBuf
///
/// 对象类型
/// 字节数组
///
public static T DeserializeFromByteAry_PB(this byte[] arr)
{
using (MemoryStream ms = new MemoryStream(arr))
return ProtoBuf.Serializer.Deserialize(ms);
}
///
/// 将对象实例序列化为二进制文件——ProtoBuf
///
/// 对象类型
/// 对象实例
/// 文件路径(目录+文件名)
public static void SerializeToFile_PB(this T obj, string path)
{
using (var file = File.Create(path))
{
ProtoBuf.Serializer.Serialize(file, obj);
}
}
///
/// 将二进制文件反序列化为对象实例——ProtoBuf
///
///
///
///
public static T DeserializeFromFile_PB(this string path)
{
using (var file = File.OpenRead(path))
{
return ProtoBuf.Serializer.Deserialize(file);
}
}
}
使用方法如下:
static void Main(string[] args)
{
var person = new Person
{
Id = 12345,
Name = "Fred",
Address = new Address
{
Line1 = "Flat 1",
Line2 = "The Meadows"
}
};
string str = person.SerializeToString_PB();
var strPerson = str.DeserializeFromString_PB();
Console.WriteLine("序列化结果(字符串):" + str);
var arr = person.SerializeToByteAry_PB();
var arrPerson = arr.DeserializeFromByteAry_PB();
Console.WriteLine("序列化结果(字节数组):" + BitConverter.ToString(arr));
string path = "person.bin";
person.SerializeToFile_PB(path);
var pathPerson = path.DeserializeFromFile_PB();
Console.WriteLine("序列化结果(二进制文件):" + BitConverter.ToString(File.ReadAllBytes(path)));
Console.ReadLine();
}
结果如下:
序列化结果(字符串):CLlgEgRGcmVkGhUKBkZsYXQgMRILVGhlIE1lYWRvd3M=
序列化结果(字节数组):08-B9-60-12-04-46-72-65-64-1A-15-0A-06-46-6C-61-74-20-31-12-0B-54-68-65-20-4D-65-61-64-6F-77-73
序列化结果(二进制文件):08-B9-60-12-04-46-72-65-64-1A-15-0A-06-46-6C-61-74-20-31-12-0B-54-68-65-20-4D-65-61-64-6F-77-73