C#中protobuf-net的编码结构及使用方法
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示例):
[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
参考资料
- ProtoBuf官网
- 【Protobuf】Protobuf的编解码规则详解——CSDN
- [翻译] ProtoBuf 官方文档(五)- 编码——简书
- 深入 ProtoBuf - 编码——简书
- protobuf-net源码及dll备份-20210410 提取码: sv1p
C#中protobuf-net的编码结构及使用方法的更多相关文章
- Java web应用中的常见字符编码问题的解决方法
以下是 Java Web应用的常见编码问题 1. html页面的编码 在web应用中,通常浏览器会根据http header: Content-type的值来决定用什么encoding, 比如遇到Co ...
- [转载]Java web应用中的常见字符编码问题的解决方法
以下是 Java web应用的常见编码问题 1. html页面的编码 在web应用中,通常浏览器会根据http header: Content-type的值来决定用什么encoding, 比如遇到Co ...
- Paper | 学习多任务中的最佳分/ 合结构(十字绣结构)
目录 1. 问题 2. 十字绣结构(Cross-stitch architecture) 3. 实验设计 论文:Cross-stitch Networks for Multi-task Learnin ...
- PHP7中Protobuf的安装使用
PHP7中Protobuf的安装使用 写这篇文章的缘由是最近在关注RPC框架序列化的一些原理.但是在安装Protobuf的时候,发现网上的教程都太老了,加上目前Protobuf官方已经支持PHP了,不 ...
- DDos攻击,使用深度学习中 栈式自编码的算法
转自:http://www.airghc.top/2016/11/10/Dection-DDos/ 最近研究了一篇论文,关于检测DDos攻击,使用了深度学习中 栈式自编码的算法,现在简要介绍一下内容论 ...
- 大厂技术实现 | 腾讯信息流推荐排序中的并联双塔CTR结构 @推荐与计算广告系列
作者:韩信子@ShowMeAI,Joan@腾讯 地址:http://www.showmeai.tech/article-detail/tencent-ctr 声明:版权所有,转载请联系平台与作者并注明 ...
- WEB开发中的字符集和编码
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...
- <<< html编码中js和html编码不一致导致乱码
在html中,有时把编码设置成UTF-8之后,引入js,页面不会有乱码,但是有关js的东西会出现乱码, 大概问题就是js默认编码不是UTF-8, 解决办法:将js文件用记事本打开,在另存为,保存的时候 ...
- js中的三个编码函数:escape,encodeURI,encodeURIComponent
1. eacape(): 该方法不会对 ASCII 字母和数字进行编码,也不会对下面这些 ASCII 标点符号进行编码: * @ - _ + . / .其他所有的字符都会被转义序列替换.其它情况下es ...
随机推荐
- position: absolute; not work
position: absolute; not work https://stackoverflow.com/questions/11928294/css-position-absolute-with ...
- Google can't be accessed again, today is shit day
Google can't be accessed again, today is shit day 2019.11.28 12:00~20:56 holy shit (pile of poop) Go ...
- 听说USDN最近很火,它究竟是什么?
最近USDN在各大社区沸沸扬扬,甚至很多人都说USDN将会打破稳定币市场的格局,那么USDN究竟是怎样的一种稳定币呢?小编今天就帮助大家了解一下,新一代算法型稳定币USDN. USDN是基于公链NGK ...
- NGK:APP一站式挖矿高收益项目
NGK是10月中旬刚上线的公链项目,采用手机挖矿形式.NGK数字增益平台,200美金即可入场,收益可观,分为静态和动态两种,投资算力收益超高.邀请好友挖矿还有额外的返佣. NGK立志为所有人创造无差别 ...
- C++算法代码——字符串p型编码
题目来自:http://218.5.5.242:9018/JudgeOnline/problem.php?id=1681 题目描述 给定一个完全由数字字符('0','1','2',-,'9')构成的字 ...
- 【commons-pool2源码】写前思考
写作的初衷 工作4年多, 一直没有系统的阅读过优秀的开源代码, 所以从今年开始做一些尝试, 阅读源码并且试着将自己的理解以文章的形式输出, 从而达到以下目的: 通过阅读源码提升自身的技术水准, 通过写 ...
- A study on ILC for linear discrete systems with single delay
论文题目就是随笔的题目,以后的随笔的命名都是如此,特此说明. 论文的主要内容是偏理论研究的,引入了离散矩阵延迟指数函数,来处理具有单时滞线性离散系统.对于离散延迟矩阵指数函数其定义为: \[e_{m} ...
- Nginx(八): 观进程锁的实现
前面的nginx系列讲解了nginx很多通用概念,流程,以及核心的http模块的一些实现.应该说大体上对nginx已经不再陌生和神秘. 今天我们不看全局,只看一个非常非常小的细节:nginx是多进程并 ...
- Pygame基础(1)
Pygame是Python的一个很常用的游戏框架,今天我来讲一讲Pygame的基础知识 Pygame的官网:https://www.pygame.org/news Pygame的下载 打开cmd输入p ...
- OAuth2.0安全设计之Authorization Code
OAuth 2.0 有 4 种认证流程: 授权码模式(authorization code) 简化模式(implicit) 密码模式(resource owner password credentia ...