PrococolBuffer使用
Google开发的ProtocolBuffer是目前使用相当广泛的跨平台序列化机制;比起Json和XML它更快、更简单,也支持自定义的数据结构。关于速度的测试 官网语法手册
1.生成供C#使用的ProtocolBuffer
对于C#来说,ProtocolBuffer会根据.proto文件生成一系列.cs文件来定义各种数据结构。在GitHub的发布页面中,可以下载到源代码与已经编译好的”protoc.exe”可执行文件,下载完(也许还要编译)之后,就可以使用来生成协议文件了。
Tip:include目录下有些Google定义的常见数据格式(时间戳之类的)。具体使用请见官方手册。
这是一个简单的协议文件的示例。syntax定义了语法版本为v3,注释与C类似,message是消息的主体,消息内能包含诸如:float,double,int64,fixed32,string等格式的数据(详见)。数组、字典、枚举由repeated,map<,>,enum支持。
import用来从从其它文件导入message等结构,import new用来处理文件的移动。
package一般用来防止命名冲突,对于C#它会成为namespace。
syntax = "proto3";
package Connect;
import "other.proto";
//向服务器握手
message CS_HandShake
{
int64 player_uuid = 1;
int64 time = 2;
}
在有了协议定义之后,就可以用protoc.exe生成对应的代码了。protoc.exe有几个命令行参数:
- –csharp_out=DST_DIR:在DST_DIR目录生成消息对应的.cs文件。
- –proto_path=IMPORT_PATH 与 -I=IMPORT_PATH(短的写法):import的文件所在的目录,可以有多个。
- 最后则是 要生成的.proto所在的目录,可以使用*.proto等通配符来生成多个。
//代码示例
./protoc.exe --proto_path=IMPORT_PATH --cpp_out=DST_DIR path/to/file.proto
我使用一个shell文件来批量生成所要的协议。我利用gitBash在windos中使用bash,因为CMD的语法不大懂。
#!/bin/bash
echo "开始生成ProtoBuff"
msg_path_array=("msg","msg2")
is_copy_2_unity=true
copy_path="../../XXX/Assets/Scripts/NetWork/Proto/"
#清空生成目录
rm -rf "./gen"
#清空拷贝目录
if $is_copy_2_unity; then
rm -rf "${copy_path}"
fi
for path in ${msg_path_array[@]}; do
echo "正在生成Protp: ${path}"
#创建目录
gen_path="./gen/${path}"
if [ ! -d "$gen_path" ]; then
echo "正在创建目录: ${gen_path}"
mkdir -p "$gen_path"
fi
#真正的生成protoBuff
./protoc.exe --proto_path="./${path}" --csharp_out="${gen_path}" "${path}/*.proto"
#拷贝
if $is_copy_2_unity; then
if [ ! -d "${copy_path}" ]; then
mkdir -p "${copy_path}"
fi
cp -r "${gen_path}" "${copy_path}"
fi
done
2.在Unity中引入ProtocolBuffer
直接把上面生成的文件放到Unity里的话会报错,因为它使用了许多ProtocolBuffer的类库,为此我们可以引入相应的dll动态链接库文件(源码也行但不自定义的话应该没啥必要吧,编译还慢),ProtocolBuffer的.net标准最早支持到.Net4.5,所以先要把Unity的目标运行时切换到4.X。
要编译库文件来使用的话,可以从下载的源码的这个位置打开VS工程,我下载了最的源码,里面有.net 6.0(netstandard2.0)的依赖,所以还必须要用VS 2022。
打开后生成一下项目就可以在Release或Debug里找到要的dll了。
项目如图所示
使用方式如下:
//创建协议对应的class
Connect.CS_HandShake msg_handShake = new Connect.CS_HandShake();
msg_handShake.PlayerUuid = uuid;
msg_handShake.Time = DateTime.UtcNow.Ticks;
//序列化
IMessage message = msg_handShake as IMessage;
byte[] data = message.ToByteArray();
//反序列化
IMessage msgData = data.messageParser.ParseFrom(data);
之后我使用UDP来发送序列化后的Byte数组,并使用枚举(int64)作为前缀来区分不同的类型。将在(二)中记录一下。