Protobuf 初识

什么是Protobuf

Google Protocol Buffers(简单Protobuf或PB) 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关 、可扩展的序列化结构数据格式 。目前,几乎支持所有主流编程语言 。

他和xml、json一样,都属于数据标记语言。

Protobuf优势

  1. 序列化与反序列化速度极快。
  2. 与语言及平台无关,兼容性好,通过 proto文件生成多种语言文件 => 实现 服务端、客户端之间 跨语言平台的数据转换。
  3. 数据高度压缩 => 占用空间少,节省带宽。

Protobuf使用

google官方全版本支持

.net专用版本 作者Marc Gravell(本篇使用)

Protobuf对比C#常规序列化

标签不同,常规只需要对类打上[Serializable],而Protobuf需要对类打上[ProtoContract],再对属性打上[ProtoMember(1)],中间的数字是tag对应一个属性。

调用不同,常规需要 Stream 配合 BinaryFormatter 来实现,protobuf的话是 Stream 配合 ProtoBuf.Serializer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ProtoBuf Serialize
byte[] bytes = null;
using (MemoryStream ms = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(ms, person);
bytes = new byte[ms.Length];
Buffer.BlockCopy(ms.GetBuffer(), 0, bytes, 0, (int)ms.Length);
}

// ProtoBuf DeSerialize
PersonInfo newPerson = new PersonInfo();
using (MemoryStream ms = new MemoryStream(bytes))
{
newPerson = ProtoBuf.Serializer.Deserialize<PersonInfo>(ms);
}

// ProtoBuf 本地存储读取
PersonInfo newPerson_file = new PersonInfo();
using (FileStream file = File.Create("person.bytes"))
{
ProtoBuf.Serializer.Serialize(file, person);
}

using (FileStream file = File.OpenRead("person.bytes"))
{
newPerson_file = ProtoBuf.Serializer.Deserialize<PersonInfo>(file);
}

字节量大幅优化,对2种bytes都进行输出,简单使用能直接缩10倍以上:

需求提纯

那么最后,我们要做什么?

  1. 使用protobuf通信,且自动化解决最基础的打标签方式,因为那太过繁琐。
  2. 需要支持多平台,所以直接排除[Serializable]

Protobuf 网络通信解决方案

1.制定协议

2.获取工具

.net专用版本 作者Marc Gravell(本篇使用) 中,编译源码获取 protoc.exe 和 压缩包中的 protogen.exe 两个文件。

3.生成代码

定义文件 .proto

相当于把之前C#实现的整个协议重写一遍。建议使用IDE VSCode来检查语法。

syntax - 使用proto package - 定义头文件名

message - 类 enum - 枚举类

required - 必须的 optional - 非必须的 repeated - 重复的,用于List

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
syntax = "proto2";

package LogicProtocol;

// enum写法
enum CMD {
LogicLogin = 1;
}

// 数据包写法(支持嵌套类)
message Pkg {
required Head head = 1;
optional Body body = 2;
}

message Head {
required CMD cmd = 1;
required int32 seq = 2;
required int32 error = 3;
}

message Body {
optional ReqLogicLogin reqLogicLogin = 1;
optional RspLogicLogin rspLogicLogin = 2;
}

使用工具 protogen.exe

protoc.exe也可以用于生成c#代码,但是数据更多,所以直接使用protogen.exe。

  1. cmd进入对应路径。
  2. 可以使用protogen -h命令来获取命令help。
  3. 输入指令.\soft\protogen.exe --csharp_out=.\ .\NetProtocol.proto来生成代码。

指令解析:

  • .\soft\protogen.exe 工具位置
  • –csharp_out=.\ 输出位置
  • .\NetProtocol.proto 文件位置

4.对比cs和proto协议区别

proto中的package转换成 namespace

为每一个class和enum打上标签 [global::ProtoBuf.ProtoContract()]。括号里面是反射时识别的类名,不写就直接是类名。

为每一个属性打上标签 [global::ProtoBuf.ProtoMember(1)]。括号里是proto文件中定义的数字。

开源地址

我的可视化GUI

我将集成这些功能到自己做的wpf工具中(.proto生成.cs)。

github地址

我的UDP C-S服务器通信解决方案

基于KCP优化UDP传输可靠性的 Unity/.net Client - .net Server 通信解决方案,可以选择proto协议,内有案例。

github地址