【Unity网络编程知识】协议生成工具Protobuf
1、什么是Protobuf
Protobuf全称是protocoI-buffers(协议缓冲区),是谷歌提供给开发者的一个开源的协议生成工具,可以基于协议配置文件生成,C++、Java、C#、Objective-C、PHP、Python、Ruby、Go 等等语言的代码文件。
2、下载和准备Protobuf工具
2.1 下载Protobuf相关内容
https://github.com/protocolbuffers/protobuf/releases/tag/v31.0
下载protobuf-31.0.zip和protoc-31.0-win64文件
2.2 准备DLL文件
步骤1,打开项目
步骤2,右键,选择生成
如果遇到报错,需要删除该文件global.json
生成成功
找到我们需要dll 文件
2.3 Protobuf编译器
3、Protobuf配置规则
3.1 配置后缀,Protobuf中配置文件的后缀统一使用
3.2 配置规则
1)规则1 注释方式
//规则一:注释方式//注释方式一/*注释方式二*/
2)规则2 第一行版本号
syntax = \"proto3\";//如果不写 默认使用proto2
3)规则3 命名空间
//规则三:命名空间package GamePlayerTest;//这决定了命名空间
4)规则4 消息类
message 类名{ 字段声明}
5)规则5 成员类型和唯一编号
//浮点数: //float、double //整数: //变长编码-int32,int64,uint32,uint64, //固定字节数-fixed32,fixed64,sfixed32,sfixed64 //其它类型: //bool,string,bytes //唯一编号配置成员时需要默认给他们一个编号 从1开始 //这些编号用于标识中的字段消息二进制格式
6)规则6 特殊标识
- required 必须赋值的字段
- optional 可以不赋值的字段
- repeated 数组
- map 字典
7) 规则7 枚举
enum 枚举名{ 常量1 = 0;//第一个常量必须映射到0 常量2 = 1;}
8)规则8 默认值
- string-空字符串
- bytes-空字节
- bool-false
- 数值 - 0
- 枚举 - 0
- message-取决于语言 c#为空
9) 规则9 允许嵌套
10)规则10 保留字段
//如果修改了协议规则删除了部分内容//为了避免更新时 重新使用 已经删除了的编号//我们可以利用 reserved 关键字来保留字段//这些内容就不能再被使用了message Foo{ reserved 2, 15, 9 to 11; reserved \"foo\",\"bar\";}
11)规则11 导入定义
import \"配置文件路径\";//如果你在某一个配置中使用了另一个配置的类型//则需要导入另一个配置文件名
完整示例
syntax = \"proto3\";//决定了proto文档的版本号//规则二:版本号//规则一:注释方式//注释方式一/*注释方式二*///规则11:导入定义import \"test2.proto\";//规则三:命名空间package GamePlayerTest;//这决定了命名空间//规则四:消息类message TestMsg{ //规则五:成员类型 和 唯一编号 //浮点数 // = 1 不代表默认值 而是代表唯一编号 方便我们进行序列化和反序列化的处理 //required 必须赋值的字段 proto2 //required float testF = 1; //C# - float //optional 可以不必须赋值的字段 optional double testD = 2; //C# - double //变长编码 //所谓变长 就是会根据 数字的大小 来使用对应的字节数来存储 1 2 4 //Protobuf帮助我们优化的部分 可以尽量少的使用字节数 来存储内容 int32 testInt32 = 3; // C# - int 它不太适用于表示负数 请使用sint32 //1 2 4 8 int64 testInt64 = 4; // C# - long 它不太适用于来表示负数 请使用sint64 //更适用于表示负数类型的整数 sint32 testSInt32 = 5; // C# - int 适用于来表示负数的整数 sint64 testSInt64 = 6; // C# - long 适用于来表示负数的整数 //无符号 变长编码 //1 2 4 uint32 testUInt = 7; // C# - uint 变长的编码 uint64 testULong = 8; // C# - ulong 变长的编码 //固定字节数的类型 fixed32 testFixed32 = 9; // C# - uint 它通常用来表示大于2的28次方的数,比uint32更有效 始终是4个字节 fixed64 testFixed64 = 10; // C# - ulong 它通常用来表示大于2的56次方的数,比uint64更有效 始终是4个字节 sfixed32 testSFixed32 = 11; // C# - int 始终4个字节 sfixed64 testSFixed64 = 12; // C# - long 始终8个字节 //其他类型 bool testBool = 13; // C# - bool string testStr = 14; // C# - string bytes testBytes = 15; //C# - BytesString 字节字符串 //数组List repeated int32 listInt = 16; // C# - 类似List的使用 //字典Dictionary map testMap = 17; //C# - 类似Dictionary的使用 //枚举成员变量声明 需要唯一编码 TestEnum testEnum = 18; //声明自定义类对象 需要唯一编码 //默认值是null TestMsg2 testMsg2 = 19; //规则9:允许嵌套 //嵌套一个类在另一个类当中 相当于是内部类 message TestMsg3{ int32 testInt32 = 1; } TestMsg3 testMsg3 = 20; //规则9:允许嵌套 enum TestEnum2{ NORMAL = 0; //第一个常量必须映射到0 BOSS = 1; } TestEnum2 testEnum2 = 21; //int32 testInt322 = 22; bool testBool2313 = 23; GameSystemTest.Heart testHeart = 24; //告诉编译器 22 被占用 不准用户使用 //之所以有这个功能 是为了在版本不匹配时 反序列化 不会出现结构不统一 //解析错误的问题 reserved 22; reserved \"testInt322\";}enum TestEnum{NORMAL = 0; //第一个常量必须为0BOSS = 1;}message TestMsg2{int32 testInt32 = 1;}
4、Protobuf协议生成
4.1 打开cmd窗口
4.2 进入protoc.exe所在文件夹(也可以直接将exe文件拖入cmd窗口中)
4.3 输入转换指令 protoc.exe -I=配置路径 --csharp_out=输出路径 配置文件名
5、Protobuf协议使用
5.1 序列化存储为本地文件
// 主要使用 //1.生成的类中的 WriteTo方法 //2.文件流FileStream对象 TestMsg msg = new TestMsg(); msg.ListInt.Add(1); msg.TestBool = true; msg.TestD = 5.5; msg.TestInt32 = 99; msg.TestMap.Add(1, \"dadad\"); msg.TestMsg2 = new TestMsg2(); msg.TestMsg2.TestInt32 = 8; msg.TestMsg3 = new TestMsg.Types.TestMsg3(); msg.TestMsg3.TestInt32 = 10; msg.TestHeart = new GameSystemTest.Heart(); msg.TestHeart.Time = 999; print(Application.persistentDataPath); using(FileStream fs = File.Create(Application.persistentDataPath + \"/TestMsg.zt\")) { msg.WriteTo(fs); }
5.2 反序列化本地文件
//主要使用 //1.生成的类中的 Parser.ParseFrom方法 //2.文件流FileStream对象 using(FileStream fs = File.OpenRead(Application.persistentDataPath + \"/TestMsg.zt\")) { TestMsg msg2 = null; msg2 = TestMsg.Parser.ParseFrom(fs); print(msg2.TestMap[1]); print(msg2.ListInt[0]); print(msg2.TestD); print(msg2.TestMsg2.TestInt32); print(msg2.TestMsg3.TestInt32); print(msg2.TestHeart.Time); }
5.3 得到序列化后的字节数组
方式一
//主要使用 //1.生成的类中的 WriteTo方法 //2.内存流MemoryStream对象 byte[] bytes = null; using (MemoryStream ms = new MemoryStream()) { msg.WriteTo(ms); bytes = ms.ToArray(); print(\"字节数组的长度\" + bytes.Length); }
方式二
byte[] bytes2 = msg.ToByteArray();
5.4 从字节数组反序列化
方式一
//主要使用 //1.生成的类中的 Parser.ParseFrom方法 //2.内存流MemoryStream对象 using(MemoryStream ms = new MemoryStream(bytes)) { print(\"内存流中反序列化的内容\"); TestMsg msg2 = TestMsg.Parser.ParseFrom(ms); print(msg2.TestMap[1]); print(msg2.ListInt[0]); print(msg2.TestD); print(msg2.TestMsg2.TestInt32); print(msg2.TestMsg3.TestInt32); print(msg2.TestHeart.Time); }
方式二
TestMsg msg3 = TestMsg.Parser.ParseFrom(bytes2); print(msg3.TestMap[1]); print(msg3.ListInt[0]); print(msg3.TestD); print(msg3.TestMsg2.TestInt32); print(msg3.TestMsg3.TestInt32); print(msg3.TestHeart.Time);
6、工具类封装
using Google.Protobuf;using System;using System.Collections;using System.Collections.Generic;using System.IO;using System.Reflection;using UnityEngine;public static class NetTool{ /// /// 序列化Protobuf生成的对象 /// /// /// public static byte[] GetProtoBytes(IMessage msg) { //基础写法 基于内存流 //byte[] bytes = null; //using(MemoryStream ms = new MemoryStream()) //{ // msg.WriteTo(ms); // bytes = ms.ToArray(); //} //return bytes; //通过该拓展方法 就可以直接获取对应对象的 字节数组 return msg.ToByteArray(); } /// /// 反序列化字节数组为Protobuf相关的对象 /// /// 想要获取的消息类型 /// 对应的字节数组 用于反序列化 /// public static T GetProtoMsg(byte[] bytes) where T : class, IMessage { //得到对应消息的类型 通过反射得到内部的静态成员 然后得到其中的 对应方法 //进行反序列化 Type type = typeof(T); //通过反射 得到对应的 静态成员属性对象 PropertyInfo pInfo = type.GetProperty(\"Parser\"); object parserObj = pInfo.GetValue(null, null); Type parserType = parserObj.GetType(); //指定得到某一个重载函数 MethodInfo mInfo = parserType.GetMethod(\"ParseFrom\", new Type[] { typeof(byte[]) }); //调用对应的方法 反序列化为指定的对象 object msg = mInfo.Invoke(parserObj, new object[] {bytes}); return msg as T; }}