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

参考资料

C#中protobuf-net的编码结构及使用方法的更多相关文章

  1. Java web应用中的常见字符编码问题的解决方法

    以下是 Java Web应用的常见编码问题 1. html页面的编码 在web应用中,通常浏览器会根据http header: Content-type的值来决定用什么encoding, 比如遇到Co ...

  2. [转载]Java web应用中的常见字符编码问题的解决方法

    以下是 Java web应用的常见编码问题 1. html页面的编码 在web应用中,通常浏览器会根据http header: Content-type的值来决定用什么encoding, 比如遇到Co ...

  3. Paper | 学习多任务中的最佳分/ 合结构(十字绣结构)

    目录 1. 问题 2. 十字绣结构(Cross-stitch architecture) 3. 实验设计 论文:Cross-stitch Networks for Multi-task Learnin ...

  4. PHP7中Protobuf的安装使用

    PHP7中Protobuf的安装使用 写这篇文章的缘由是最近在关注RPC框架序列化的一些原理.但是在安装Protobuf的时候,发现网上的教程都太老了,加上目前Protobuf官方已经支持PHP了,不 ...

  5. DDos攻击,使用深度学习中 栈式自编码的算法

    转自:http://www.airghc.top/2016/11/10/Dection-DDos/ 最近研究了一篇论文,关于检测DDos攻击,使用了深度学习中 栈式自编码的算法,现在简要介绍一下内容论 ...

  6. 大厂技术实现 | 腾讯信息流推荐排序中的并联双塔CTR结构 @推荐与计算广告系列

    作者:韩信子@ShowMeAI,Joan@腾讯 地址:http://www.showmeai.tech/article-detail/tencent-ctr 声明:版权所有,转载请联系平台与作者并注明 ...

  7. WEB开发中的字符集和编码

    html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...

  8. <<< html编码中js和html编码不一致导致乱码

    在html中,有时把编码设置成UTF-8之后,引入js,页面不会有乱码,但是有关js的东西会出现乱码, 大概问题就是js默认编码不是UTF-8, 解决办法:将js文件用记事本打开,在另存为,保存的时候 ...

  9. js中的三个编码函数:escape,encodeURI,encodeURIComponent

    1. eacape(): 该方法不会对 ASCII 字母和数字进行编码,也不会对下面这些 ASCII 标点符号进行编码: * @ - _ + . / .其他所有的字符都会被转义序列替换.其它情况下es ...

随机推荐

  1. NGK公链有发展前景吗?

    最近网络中经常能看到一个新公链项目NGK的消息,很多朋友也都私下表示过,非常看好今年的NGK.对此,小编对NGK做了一些功课,发觉到NGK未来在商业Dapp应用的发展前景,下面就给大家分享一下我的理解 ...

  2. 链表、栈、队列、KMP相关知识点

    链表.栈与队列.kmp; 数组模拟单链表: 用的最多的是邻接表--就是多个单链表: 作用:存储树与图 需要明确相关定义: 为什么需要使用数组模拟链表 比使用结构体 或者类来说 速度更快 代码简洁 算法 ...

  3. C++实现String类

    1 #include<iostream> 2 #include<cstring> 3 4 class String 5 { 6 public: 7 String(); 8 St ...

  4. 官网GitLab CI/CD英文文档翻译

    在查阅GitLab官网的CI/CD功能说明时,全是英文看起来不方便,通过翻译软件自动翻译后"内容失真",看起来很变扭.查阅了百度上的资料发现很多翻译很老旧,有些甚至是挂羊头卖狗肉. ...

  5. ElasticSearch 搜索模板与建议

    公号:码农充电站pro 主页:https://codeshellme.github.io Search APIs 用于搜索和聚合存储在 ES 中的数据. 1,搜索模板 Template Search ...

  6. DQL:data query language用来查询数据库表中的数据

    对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 如果没有查询条件,则每次查询所有的行.实际应用中,一般要指定查询的条件.对记录进行过滤. 查询 ...

  7. 使用python进行接口自动化测试,批量执行测试用例

    工作中,使用python的requests库进行接口自动化测试是一个比较不错的选择,今天就以某网站的免费接口为例,展示以get请求进行批量执行测试用例.话不多说直接开讲 分析一下接口信息, 请求地址: ...

  8. 【图像处理】使用OpenCV+Python进行图像处理入门教程(三)色彩空间

    这篇随笔介绍使用OpenCV进行图像处理的第三章 色彩空间. 3  色彩空间 之前的介绍,大多是基于BGR色彩空间进行的,但针对不同的实际情况,研究人员提出了许多色彩空间,它们都有各自擅长处理的领域. ...

  9. Java开发工程师最新面试题库系列——Mybatis框架部分(附答案)

    Mybatis Mybatis是什么框架? 答:持久层框架 Mybatis和ORM有什么区别? 答:ORM是对象关系映射的一种设计理念,也就是对象属性对应数据库字段,让开发人员以操作对象的方式操作数据 ...

  10. CVE-2017-10271 XMLDecoder 反序列化

    漏洞描述:WebLogic的 WLS Security组件对外提供webservice服务,其中使用了XMLDecoder来解析用户传入的XML数据,在解析的过程中出现反序列化漏洞,可以构造请求对运行 ...