C#中protobuf-net的编码结构及使用方法
2021/7/19 9:36:02
本文主要是介绍C#中protobuf-net的编码结构及使用方法,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
protobuf-net简介#
Protocol Buffer(简称Protobuf) 是 Google 公司内部提供的数据序列化和反序列化标准,与 JSON 和 XML 格式类似,同样大小的对象,相比 XML 和 JSON 格式, Protobuf 序列化后所占用的空间最小。
Protocol Buffers 是一种轻便高效的结构化数据存储格式,可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
protobuf-net是vb.net教程用于.NET代码的基c#教程于契约的序列化程python基础教程序,它以Google设计的“protocol buffers”序列化格java基础教程式写入数据,适用于大sql教程多数编写标准类型并可以使用属性的.NET语言。
protobuf-net可通过NuGet安装程序包,也可直接访问github下载源码:https://github.com/protobuf-net/protobuf-net 。
ProtoBuf编码原理#
这里只是简单介绍一下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可用的类型如下:
Type | Meaning | Used For |
---|---|---|
0 | Varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 | 64-bit | fixed64, sfixed64, double |
2 | Length-delimited | string, bytes, embedded messages, packed repeated fields |
3 | Start group | groups (deprecated,遗弃) |
4 | End group | groups (deprecated,遗弃) |
5 | 32-bit | vfixed32, sfixed32, float |
Varints 编码:在每个字节开头的 bit 设置了 msb(most significant bit ),标识是否需要继续读取下一个字节,存储数字对应的二进制补码,补码的低位排在前面,类似小端模式。
ZigZag 编码:有符号整数映射到无符号整数,然后再使用 Varints 编码,sint32、sint64 将采用 ZigZag 编码(编码结构依然为 Tag - Value)。
解析一个编码结果#
准备一个Person类(来自github示例):
[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
- 第1个字节 00001000 :表示filed_name=1,write_type=0,既Id字段的Tag;
- 第2个字节 10111001 :Id字段的Value,高位1表示继续读取下一字节;
- 第3个字节 01100000 :Id字段的Value的高位,高位0表示不继续读取下一字节,组合后的值为1100000 0111001(Varints 编码),十进制值为12345;
- 第4个字节 00010010 :表示filed_name=2,write_type=2(需显式告知长度),既Name字段的Tag;
- 第5个字节 00000100 :Name字段的Length,高位0表示不继续读取下一字节,长度为4;
- 第6-9个字节 46-72-65-64 :Name字段的Value,"Fred"的ASCII码;
- 第10个字节 00011010 :表示filed_name=3,write_type=2,既Address字段的Tag;
- 第11个字节 00010101 :Address字段的Length,高位0表示不继续读取下一字节,长度为21;
- 第12个字节 00001010 :表示filed_name=1,write_type=2,既Address的Line1字段的Tag;
- 第13个字节 00000110 :Address的Line1字段的Length,高位0表示不继续读取下一字节,长度为6;
- 第14-19个字节 46-6C-61-74-20-31 :Address的Line1字段的Value,"Flat 1"的ASCII码;
- 第20个字节 00010010 : 表示filed_name=2,write_type=2,既Address的Line2字段的Tag;
- 第21个字节 00001011 :Address的Line2字段的Length,高位0表示不继续读取下一字节,长度为11;
- 第22-32个字节 54-68-65-20-4D-65-61-64-6F-77-73 :Address的Line2字段的Value,"The Meadows"的ASCII码。
使用方法#
下面是一个ProtoBuf-Net的扩展方法类,提供了字符串、字节数组、二进制文件与对象实例之间的互相转换方法,代码如下:
using System; using System.IO; /* * 博客园首发 https://www.cnblogs.com/timefiles/ * 创建时间:2021-04-10 */ /// <summary> /// ProtoBuf-Net扩展方法类 /// </summary> public static class ProtoBufExtension { /// <summary> /// 将对象实例序列化为字符串(Base64编码格式)——ProtoBuf /// </summary> /// <typeparam name="T">对象类型</typeparam> /// <param name="obj">对象实例</param> /// <returns>字符串(Base64编码格式)</returns> public static string SerializeToString_PB<T>(this T obj) { using (MemoryStream ms = new MemoryStream()) { ProtoBuf.Serializer.Serialize(ms, obj); return Convert.ToBase64String(ms.GetBuffer(), 0, (int)ms.Length); } } /// <summary> /// 将字符串(Base64编码格式)反序列化为对象实例——ProtoBuf /// </summary> /// <typeparam name="T">对象类型</typeparam> /// <param name="txt">字符串(Base64编码格式)</param> /// <returns>对象实例</returns> public static T DeserializeFromString_PB<T>(this string txt) { byte[] arr = Convert.FromBase64String(txt); using (MemoryStream ms = new MemoryStream(arr)) return ProtoBuf.Serializer.Deserialize<T>(ms); } /// <summary> /// 将对象实例序列化为字节数组——ProtoBuf /// </summary> /// <typeparam name="T">对象类型</typeparam> /// <param name="obj">对象实例</param> /// <returns>字节数组</returns> public static byte[] SerializeToByteAry_PB<T>(this T obj) { using (MemoryStream ms = new MemoryStream()) { ProtoBuf.Serializer.Serialize(ms, obj); return ms.ToArray(); } } /// <summary> /// 将字节数组反序列化为对象实例——ProtoBuf /// </summary> /// <typeparam name="T">对象类型</typeparam> /// <param name="arr">字节数组</param> /// <returns></returns> public static T DeserializeFromByteAry_PB<T>(this byte[] arr) { using (MemoryStream ms = new MemoryStream(arr)) return ProtoBuf.Serializer.Deserialize<T>(ms); } /// <summary> /// 将对象实例序列化为二进制文件——ProtoBuf /// </summary> /// <typeparam name="T">对象类型</typeparam> /// <param name="obj">对象实例</param> /// <param name="path">文件路径(目录+文件名)</param> public static void SerializeToFile_PB<T>(this T obj, string path) { using (var file = File.Create(path)) { ProtoBuf.Serializer.Serialize(file, obj); } } /// <summary> /// 将二进制文件反序列化为对象实例——ProtoBuf /// </summary> /// <typeparam name="T"></typeparam> /// <param name="path"></param> /// <returns></returns> public static T DeserializeFromFile_PB<T>(this string path) { using (var file = File.OpenRead(path)) { return ProtoBuf.Serializer.Deserialize<T>(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<Person>(); Console.WriteLine("序列化结果(字符串):" + str); var arr = person.SerializeToByteAry_PB(); var arrPerson = arr.DeserializeFromByteAry_PB<Person>(); Console.WriteLine("序列化结果(字节数组):" + BitConverter.ToString(arr)); string path = "person.bin"; person.SerializeToFile_PB(path); var pathPerson = path.DeserializeFromFile_PB<Person>(); 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
参考资料#
这篇关于C#中protobuf-net的编码结构及使用方法的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-03-01沐雪多租宝商城源码从.NetCore3.1升级到.Net6的步骤
- 2024-05-08首个适配Visual Studio平台的国产智能编程助手CodeGeeX正式上线!C#程序员必备效率神器!
- 2024-03-30C#设计模式之十六迭代器模式(Iterator Pattern)【行为型】
- 2024-03-29c# datetime tryparse
- 2024-02-21list find index c#
- 2024-01-24convert toint32 c#
- 2024-01-24Advanced .Net Debugging 1:你必须知道的调试工具
- 2024-01-24.NET集成IdGenerator生成分布式全局唯一ID
- 2024-01-23用CI/CD工具Vela部署Elasticsearch + C# 如何使用
- 2024-01-23.NET开源的简单、快速、强大的前后端分离后台权限管理系统