经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C# » 查看文章
C#中protobuf-net的编码结构及使用方法 - time-flies
来源:cnblogs  作者:time-flies  时间:2021/4/12 10:01:56  对本文有异议

protobuf-net简介

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的大致编码过程,具体编码规则参考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示例):

  1. [ProtoContract]
  2. class Person
  3. {
  4. [ProtoMember(1)]
  5. public int Id { get; set; }
  6. [ProtoMember(2)]
  7. public string Name { get; set; }
  8. [ProtoMember(3)]
  9. public Address Address { get; set; }
  10. }
  11. [ProtoContract]
  12. class Address
  13. {
  14. [ProtoMember(1)]
  15. public string Line1 { get; set; }
  16. [ProtoMember(2)]
  17. public string Line2 { get; set; }
  18. }

实例化并赋值:

  1. var person = new Person
  2. {
  3. Id = 12345,
  4. Name = "Fred",
  5. Address = new Address
  6. {
  7. Line1 = "Flat 1",
  8. Line2 = "The Meadows"
  9. }
  10. };

序列化后的结果:

  1. //十六进制
  2. 08-B9-60-12-04-
  3. 46-72-65-64-1A-
  4. 15-0A-06-46-6C-
  5. 61-74-20-31-12-
  6. 0B-54-68-65-20-
  7. 4D-65-61-64-6F-
  8. 77-73
  9. //二进制
  10. 00001000-10111001-01100000-00010010-00000100-
  11. 01000110-01110010-01100101-01100100-00011010-
  12. 00010101-00001010-00000110-01000110-01101100-
  13. 01100001-01110100-00100000-00110001-00010010-
  14. 00001011-01010100-01101000-01100101-00100000-
  15. 01001101-01100101-01100001-01100100-01101111-
  16. 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的扩展方法类,提供了字符串、字节数组、二进制文件与对象实例之间的互相转换方法,代码如下:

  1. using System;
  2. using System.IO;
  3. /*
  4. * 博客园首发 https://www.cnblogs.com/timefiles/
  5. * 创建时间:2021-04-10
  6. */
  7. /// <summary>
  8. /// ProtoBuf-Net扩展方法类
  9. /// </summary>
  10. public static class ProtoBufExtension
  11. {
  12. /// <summary>
  13. /// 将对象实例序列化为字符串(Base64编码格式)——ProtoBuf
  14. /// </summary>
  15. /// <typeparam name="T">对象类型</typeparam>
  16. /// <param name="obj">对象实例</param>
  17. /// <returns>字符串(Base64编码格式)</returns>
  18. public static string SerializeToString_PB<T>(this T obj)
  19. {
  20. using (MemoryStream ms = new MemoryStream())
  21. {
  22. ProtoBuf.Serializer.Serialize(ms, obj);
  23. return Convert.ToBase64String(ms.GetBuffer(), 0, (int)ms.Length);
  24. }
  25. }
  26. /// <summary>
  27. /// 将字符串(Base64编码格式)反序列化为对象实例——ProtoBuf
  28. /// </summary>
  29. /// <typeparam name="T">对象类型</typeparam>
  30. /// <param name="txt">字符串(Base64编码格式)</param>
  31. /// <returns>对象实例</returns>
  32. public static T DeserializeFromString_PB<T>(this string txt)
  33. {
  34. byte[] arr = Convert.FromBase64String(txt);
  35. using (MemoryStream ms = new MemoryStream(arr))
  36. return ProtoBuf.Serializer.Deserialize<T>(ms);
  37. }
  38. /// <summary>
  39. /// 将对象实例序列化为字节数组——ProtoBuf
  40. /// </summary>
  41. /// <typeparam name="T">对象类型</typeparam>
  42. /// <param name="obj">对象实例</param>
  43. /// <returns>字节数组</returns>
  44. public static byte[] SerializeToByteAry_PB<T>(this T obj)
  45. {
  46. using (MemoryStream ms = new MemoryStream())
  47. {
  48. ProtoBuf.Serializer.Serialize(ms, obj);
  49. return ms.ToArray();
  50. }
  51. }
  52. /// <summary>
  53. /// 将字节数组反序列化为对象实例——ProtoBuf
  54. /// </summary>
  55. /// <typeparam name="T">对象类型</typeparam>
  56. /// <param name="arr">字节数组</param>
  57. /// <returns></returns>
  58. public static T DeserializeFromByteAry_PB<T>(this byte[] arr)
  59. {
  60. using (MemoryStream ms = new MemoryStream(arr))
  61. return ProtoBuf.Serializer.Deserialize<T>(ms);
  62. }
  63. /// <summary>
  64. /// 将对象实例序列化为二进制文件——ProtoBuf
  65. /// </summary>
  66. /// <typeparam name="T">对象类型</typeparam>
  67. /// <param name="obj">对象实例</param>
  68. /// <param name="path">文件路径(目录+文件名)</param>
  69. public static void SerializeToFile_PB<T>(this T obj, string path)
  70. {
  71. using (var file = File.Create(path))
  72. {
  73. ProtoBuf.Serializer.Serialize(file, obj);
  74. }
  75. }
  76. /// <summary>
  77. /// 将二进制文件反序列化为对象实例——ProtoBuf
  78. /// </summary>
  79. /// <typeparam name="T"></typeparam>
  80. /// <param name="path"></param>
  81. /// <returns></returns>
  82. public static T DeserializeFromFile_PB<T>(this string path)
  83. {
  84. using (var file = File.OpenRead(path))
  85. {
  86. return ProtoBuf.Serializer.Deserialize<T>(file);
  87. }
  88. }
  89. }

使用方法如下:

  1. static void Main(string[] args)
  2. {
  3. var person = new Person
  4. {
  5. Id = 12345,
  6. Name = "Fred",
  7. Address = new Address
  8. {
  9. Line1 = "Flat 1",
  10. Line2 = "The Meadows"
  11. }
  12. };
  13. string str = person.SerializeToString_PB();
  14. var strPerson = str.DeserializeFromString_PB<Person>();
  15. Console.WriteLine("序列化结果(字符串):" + str);
  16. var arr = person.SerializeToByteAry_PB();
  17. var arrPerson = arr.DeserializeFromByteAry_PB<Person>();
  18. Console.WriteLine("序列化结果(字节数组):" + BitConverter.ToString(arr));
  19. string path = "person.bin";
  20. person.SerializeToFile_PB(path);
  21. var pathPerson = path.DeserializeFromFile_PB<Person>();
  22. Console.WriteLine("序列化结果(二进制文件):" + BitConverter.ToString(File.ReadAllBytes(path)));
  23. Console.ReadLine();
  24. }

结果如下:

  1. 序列化结果(字符串):CLlgEgRGcmVkGhUKBkZsYXQgMRILVGhlIE1lYWRvd3M=
  2. 序列化结果(字节数组):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
  3. 序列化结果(二进制文件):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

参考资料

原文链接:http://www.cnblogs.com/timefiles/p/protobuf-net.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号