在网络传输的过程中 数据会比较多 处理起来会比较麻烦,因此得引入分层的概念,
计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决
“Any problem in computer science can be solved by anther layer of indirection.”
在客服端 和 服务器可以制订一套通信协议, 通信协议是客服端和服务端 共同约定的一个 数据结构,其包含了双方可以发送,并对方可以识别处理的数据包。
Protobuffer 可以把 一个类的实例序列化,和反序列化, 通过反射机制去取得, 开源的。
也支持JsonMap
github源码
2019最新版是 3.7,需要.Net 4.5以上
API
protocolbuffer(以下简称PB)是google 的一种数据交换的格式,它独立于语言,独立于平台。google
提供了多种语言的实现:java、c#、c++、go 和
python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用 xml
进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。
对一个数据类进行 序列化
项目要添加Pb的引用才可以
using ProtoBuf;
using ProtoBuf.Meta;
[ProtoContract]
class NetMode
{
[ProtoMember(1)]
public int id;
[ProtoMember(2)]
public string name;
//如果要使用构造函数,那么 默认构造函数 必须得存在,这个是一个小坑不然会报错
public NetMode()
{
}
public NetMode(int id,string name)
{
this.id = id;
this.name = name;
}
}
另外如果有数据类有子类继承, 需要在父类上添加特性 [ProtoInclude(number, typeof(子类名))]
为啥呢?
因为 这个 [ProtoContract] 特性 不允许子类继承,也不允许 多个存在
如果 数据类不使用构造函数 则不会报错
Untiy中其实也有序列化特性
不过 Pb 还加了反射技术,因此客服端服务器 两边的数据结构 其实是通用的,不然也用不了Pb,
[ProtoContract]特性 约定特性,可以用于 类 结构体,枚举,接口
序列化对象
/// <summary>
/// 通过Protobuffer 序列化对象 返回byte[]数组
/// </summary>
/// <param name="value"></param>
/// <returns>二进制数据</returns>
public static byte[] PBSerialize(object value)
{
byte[] data = null;
//在范围结束时 处理对象
using (MemoryStream ms=new MemoryStream())
{
if (value!=null)
{
// 序列化 到内存里
Serializer.Serialize(ms, value); //此方法 会调用到以下方法. 实际上是同个方法,多层封装
RuntimeTypeModel.Default.Serialize(ms, value);
}
//设置当前 位置
ms.Position = 0;
//写入 数组
long length = ms.Length;
data = new byte[length];
//从内存流中读取 写入到buffer中
ms.Read(data, 0, (int)length);
}
return data;
}
public static byte[] Serialize(object obj)
{
using (var memory = new MemoryStream())
{
Serializer.Serialize(memory, obj);
return memory.ToArray();
}
}
序列化底层:
序列化方法, 使用 ProtoWriter 去写进 内存里
把一个null类型强转为 另一个 类型具体是?
序列化 核心部分
默认序列化内容。
获取缓存区。
Interlocked.Exchange: 将对象设置为指定值,并作为原子操作返回对原始对象的引用
如果 原始对象的应用不为null 就返回该缓冲区。
缓冲池子 大小是20
释放 缓冲池
比较两个对象以获得引用相等性,如果它们相等,则替换其中一个对象、
检查序列化状态,序列化内容在使用后不能更改,如果使用就抛出 throw new InvalidOperationException(“The serialization-context cannot be changed once it is in use”);异常。
反序列化方法
public static T Deserialize<T>(byte[] data)
{
using (var memory = new MemoryStream(data))
return Serializer.Deserialize<T>(memory);
}
public static T Deserialize<T>(byte[] data, int offset, int size)
{
using (var memory = new MemoryStream(data, offset, size))
return Serializer.Deserialize<T>(memory);
}
public static object PBDSerialize( byte[] value ,Type type)
{
object o = null;
using (var memory=new MemoryStream(value))
{
o= RuntimeTypeModel.Default.Deserialize(memory,null, type);
}
return o;
}
///
/// 一个类 上的特性上的 编号 都不能重复 ,0 被视为不是int ,0 会报错
///
[ProtoInclude(3,typeof(NetModeSub))]
[ProtoInclude(3,typeof(NetModeSub))]
/// <summary>
/// 一个类 上的特性上的 编号 都不能重复 0 不是int 0 会报错
/// </summary>
[ProtoInclude(3,typeof(NetModeSub))]
[ProtoContract]
class NetMode
{
[ProtoMember(1)]
public int id;
[ProtoMember(2)]
public string name;
public NetMode()
{
}
public NetMode(int id,string name)
{
this.id = id;
this.name = name;
}
}
[ProtoContract]
class NetModeSub:NetMode
{
[ProtoMember(2)] public float range;
/// <summary>
/// 子类的构造函数也一定要有,不然也会报错
/// </summary>
public NetModeSub()
{
range = 12;
}
public NetModeSub(float range)
{
this.range = range;
}
public NetModeSub(int id, string name, float range) : base(id, name)
{
this.range = range;
}
}
另外类似的还有 结构体 枚举 等,使用的 时候 都需要添加上 [ProtoContract] 特性, 其中的数组也要加上 [ProtoMember(1)], 如果不添加 [ProtoMember]特性,则 不会把值 序列化,相当于 是默认值。
以下是一个结构体 的用法,和上面 数据类用法的一致 的。
[ProtoContract]
public struct Vector3
{
/// <summary>
/// 结构体中 需要序列化的数组 都需要 加上 ProtoMember 特性, 否则 序列化后再 反序列化数值 会 丢失,变成默认值;
/// </summary>
[ProtoMember(1)]
public float x;
[ProtoMember(2)]
public float y;
[ProtoMember(3)]
public float z;
public Vector3(float x, float y, float z)
{
this.x = x;
this.y = y;
this.z = z;
}
public override string ToString()
{
return string.Format("({0},{1},{2})", x, y, z);
}
public float this[int index]
{
get
{
switch (index)
{
case 0:
return this.x;
case 1:
return this.y;
case 2:
return this.z;
default:
throw new IndexOutOfRangeException("Invalid Vector3 index!");
}
}
set
{
switch (index)
{
case 0:
this.x = value;
break;
case 1:
this.y = value;
break;
case 2:
this.z = value;
break;
default:
throw new IndexOutOfRangeException("Invalid Vector3 index!");
}
}
}
}
枚举的处理:
这里的ProtoContract 可以不用加。
https://github.com/qq21/ProtobufLearning
说说常见问题: 一个数据类里的 声明一个V3的结构体,并且给了它默认值 (1,2,3)
它的构造函数,
然后在 使用它的时候,
这个时候 序列化前 给它赋了下值,(0,0,0)
但是在序列化后,它的值就变成了最初始的值的, 即在数据类里的初始值。
可以这么理解,原始数据就是(1,2,3) 当 值变成0的时候,就是该原始数据的起点。
也就是默认值。