Unity3D C# Socket通信详解之Protobuf.net使用详解

2020年8月23日 0 作者 老王

1 介绍

Protobuf实际是一套类似Json或者XML的数据传输格式和规范,用于不同应用或进程之间进行通信时使用。通信时所传递的信息是通过Protobuf定义的message数据结构进行打包,然后编译成二进制的码流再进行传输或者存储。

protobuf全称Protocol Buffers,简称GPB、PB,是QQ等IM采用的协议,比XML、XMPP(环信)、JSON、结构体等所有传输效果都高的一种传输协议,由谷歌发明,其效率一般是XM的20倍以上,JSON的10倍以上,是一种游戏中普遍采用的消息协议。

简单说就是Google公司发明的,用来进行序列化和反序列化的。
和json、xml类似,Protobuf有几个突出的优点:

  • 序列化后的数据更小,消息大小只需要XML的1/10 ~ 1/3
  • 解析速度更快,解析速度比XML快20 ~ 100倍
  • 兼容性更好,可以很好的向上或向下兼容
  • 跨平台的,只需定义一份proto文件,即可编译成各种语言的源码

当然,其也有一定的缺点:由于序列化为了二进制数组,序列化后完全不可读。

Protobuf-Net是Marc Gravell在protobuf基础上修改的.net版本,相比Google的代码,其在.Net环境下使用起来更加简单。本篇文章就介绍一下Protobuf的使用。
Protobuf-Net的Github地址

2 Protobuf-Net的使用

2.1 安装

我们在进行vs开发的时候,安装Protobuf-Net很简单,直接在Nuget包管理器中搜索Protobuf-Net即可。
如图。
1.进入包管理器进入包管理器
2.搜索Protobuf-Net,并安装
Protobuf-Net的安装

2.2 语法

2.2.1 序列化

核心代码就是Serializer.Serialize方法,该方法有两个参数,第二个参数是要序列化的对象,第一个参数是要序列化到的流对象。
例如序列化FileSteam,序列化完成后将直接保存至本地,不必再调用fs.Write、fs.Flush等方法。

using (FileStream fs = File.Create(path))
{
    Serializer.Serialize(fs, t);
}

也可使用MemoryStram配合Protobuf-Net来实现对象的深拷贝,就是先将要拷贝的对象序列化然后在反序列化出来。

public static T Clone<T>(T t)
{
    using (MemoryStream ms = new MemoryStream())
    {
        Serializer.Serialize(ms, t);
        ms.Position = 0;
        return Serializer.Deserialize<T>(ms);
    }
}

或者直接使用Serializer.DeepClone(t)方法(其内部实现与上面我们的方法相同)。

2.2.2 反序列化

反序列化也很简单,核心代码是Serializer.Deserialize(),传入流对象,即可得到反序列化后的对象。
如反序列化文件。

public static T Read<T>(string path)
{
    using (FileStream fs = File.OpenRead(path))
    {
        return Serializer.Deserialize<T>(fs);
    }
}

2.2.3 类处理

对于需要序列化的类,需要添加上对应的特性。
一是给类添加[ProtoBuf.ProtoContract]特性,类中的字段或属性添加[ProtoBuf.ProtoMember(1)]特性。
如下:

[ProtoBuf.ProtoContract]
internal class Student : Pearson
{
    [ProtoBuf.ProtoMember(1, IsRequired = true)]
    public int Id { get; set; }

    public Student() { }

    public override string ToString()
    {
        return $"id:{Id} name:{Name} age:{Age} gender:{Gender}";
    }
}

其中[ProtoBuf.ProtoMember(1, IsRequired = true)],数字1表示tag,只能为正数,建议从1开始;IsRequired表示是否必要,方便向后兼容,什么意思呢?比方说我们之前发布了一个版本,协议已经定好,现在需要在类中添加一些内容,这些内容就需要加上IsRequired = false,这样我们的程序虽然添加了新的内容,但是仍然可以打开之前的数据而不会报错

二是在有继承的情况时,基类需要添加上[ProtoBuf.ProtoInclude(100, typeof(Student))]特性。
一个基类可能有多个子类,有几个子类,基类上就添加几个[ProtoBuf.ProtoInclude(100, typeof(Student))]。
其中的100也是tag,和ProtoBuf.ProtoMember的tag类似,但是ProtoBuf.ProtoInclude的tag要是唯一的,不能和基类上其他的ProtoBuf.ProtoInclude的tag相同,也不能和基类中的ProtoBuf.ProtoMember的tag相同。typeof(T),表示已知的子类。

public enum Gender
{
    Male,
    Female
}

[ProtoBuf.ProtoContract]
[ProtoBuf.ProtoInclude(100, typeof(Student))]
[ProtoBuf.ProtoInclude(101, typeof(Teacher))]   // 如这里tag不能设为100、1、2、3
class Pearson
{
    [ProtoBuf.ProtoMember(1, IsRequired = false)]
    public string Name { get; set; }
    [ProtoBuf.ProtoMember(2, IsRequired = true)]
    public Gender Gender { get; set; }
    [ProtoBuf.ProtoMember(3, IsRequired = true)]
    public int Age { get; set; }
}

完整的测试项目在这。
链接:https://pan.baidu.com/s/16ri31b8F2UxvR5IDxbQP_A
提取码:5sky

3 Unity下使用Protobuf-Net

TODO 待完成。