本章主要讲述C#服务器开发中使用的序列化与反序列化工具protobuf的使用。
一 Protobuf介绍
(一)Protobuf简介
Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。
Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++,Java,Python和C#等语言的 API。
(二)Protobuf的特点
(三)Protobuf语法规则
(1)指定字段类型
所有的字段都有标量类型:string,bool等等,当然,你也可以为字段指定其他的合成类型,包括枚举或其他消息类型。
(2)分配标识号
在消息定义中,每个字段都有唯一的一个标识号,这些标识号是用来在消息的二进制格式中识别各个字段,一旦开始使用就不能修改。
!注:[1,15]之内的标识号在编码的时候会占用一个字节,[16,2047]之内的标识号则占用两个字节,所以频繁使用的元素使用[1,15]之内的标识号。
!切记:要为将来有可能使用的元素预留一些标识号。最小的标识号从1开始,最大的到229-1即536,870,911,不可以使用19000-19999的标识号,Protobuf协议中对这些标识号进行了预留,如果使用,编译时就会报错。
(3)指定字段规则
所指定的消息字段修饰符必须如下之一:
*required:不可增加或删除的字段,必须初始化。
*optional:可选字段,可删除,可以不初始化。
*repeated:可重复字段(对应C#里面的list)。
(4)标量数值类型
一个标量消息字段可以含有一个如下的类型,该表格展示.proto文件中的类型以及与之对应的,在自动生成的访问类中的类型:
(5)添加注释
向proto文件添加注释,也跟C#语法一样(//),如:
protoTest.proto
syntax="proto2";
message SearchRequest
{
required string query=1[default="hahhah"];//默认值放在default
}
(6)枚举
protoTest.proto
syntax="proto2";
message SearchRequest
{
// required string query=1[default="hahhah"];//默认值放在default
enum Person{
AGE=0;
NAME=1;
SEX=2;
}
}
(7)嵌套类型
protoTest.proto
syntax="proto2";
package Cmd;//相当于命名空间
message SearchRequest
{
required string query=1[default="hahhah"];//默认值放在default
enum Person{
AGE=0;
NAME=1;
SEX=2;
}
message Model
{
required string Line1=1;
required string Line2=2;
}
}
(8)引用
protoTest1.proto
syntax="proto2";
package Cmd;
import "protoTest.proto";
message Test
{
optional SearchRequest searchRequest=1;
}
(四)Protobuf-net下载地址
(五)Protobuf编译cs文件
(1)解压下载的压缩包,找到protobuf-net-master\src\protogen.site\wwwroot\protogen文件下的protogen各版本压缩包。我们能够借助protogen将.proto转换为.cs文件。从中选择一个版本解压缩,解压后在加压的文件下的net46文件夹下新建c#和proto两个文件夹,将刚才创建的两个protoTest.proto和prototest1.proto复制到proto文件夹下。
(2)运行命令行,先cd到protogen.exe所在的文件夹,然后输入protogen.exe --proto_path=./proto --csharp_out=./c# protoTest1.proto,其中–proto_path=./proto指定要编译的文件目录,–csharp_out=./c#指定编译结束后输出目录,protoTest1.proto指示要编译的文件。
当输出generated: ./c#\protoTest1.cs说明编译成功,可以查看到c#文件加下生成了一个protoTest1.cs文件。
二 利用Protobuf进行序列化与反序列化
VS C#控制台程序
(1)模板 Person.cs(通过Protogen.exe编译出来的.cs文件)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ProtoBuf;
namespace ProtoTest
{
[ProtoContract]//在需要序列花的类签名上面加上[ProtoContract]
class Person
{
[ProtoMember(1)]//在类的字段签名上加[ProtoMeber(i)]每个都加括号的数字增加
public string Name { get; set; }
[ProtoMember(2)]
public int age { get; set; }
[ProtoMember(3)]
public int weight { get; set; }
public override string ToString()
{
return "Name:" + Name + "," + "age:" + age + "," + "weight:" + weight;
}
}
}
(2)ProtobufHelper.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace ProtoTest
{
class ProtobufHelper
{
/// <summary>
/// 序列化为string
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static string Serialize<T>(T t)
{
using (MemoryStream ms = new MemoryStream())
{
Serializer.Serialize<T>(ms, t);
return Encoding.UTF8.GetString(ms.ToArray());
}
}
/// <summary>
/// 序列化到文件
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="path"></param>
/// <param name="t"></param>
public static void Serialize<T>(string path, T t)
{
using (Stream file = File.Create(path))
{
Serializer.Serialize<T>(file, t);
}
}
/// <summary>
/// 字符串反序列化对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="content"></param>
/// <returns></returns>
public static T DeSerialize<T>(string content)
{
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(content)))
{
T t = Serializer.Deserialize<T>(ms);
return t;
}
}
/// <summary>
/// 文件反序列化对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="filestream"></param>
/// <returns></returns>
public static T DeSerialize<T>(Stream filestream)
{
return Serializer.Deserialize<T>(filestream);
}
}
}
(3)Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace ProtoTest
{
class Program
{
static void Main(string[] args)
{
Person one = new Person() { Name = "A", age = 18,weight=40};
Person two = new Person() { Name = "B", age = 15,weight=44};
List<Person> people = new List<Person>() { one, two };
//序列化字符
string temp = ProtobufHelper.Serialize<List<Person>>(people);
//序列化到文件
ProtobufHelper.Serialize<List<Person>>("Test.txt",people);
Console.WriteLine(temp);
//反序列化字符
List<Person> newlist=ProtobufHelper.DeSerialize<List<Person>>(temp);
for (int i = 0; i < newlist.Count; i++)
{
Console.WriteLine(newlist[i].ToString());
}
//反序列化字符
List<Person> newlist1 = ProtobufHelper.DeSerialize<List<Person>>(new FileInfo("Test.txt").OpenRead());
for (int i = 0; i < newlist1.Count; i++)
{
Console.WriteLine(newlist1[i].ToString());
}
while (true)
{ }
}
}
}