Unity使用ProtoBuf
1 Protocol Buffers (protobuf) 和 protobuf-net
1.0 protobuf的优势
使用 Protocol Buffers(protobuf)在多种场景中具有显著的优势,特别是在需要高效、紧凑的数据序列化和反序列化的情况下。以下是使用 protobuf 的一些常见场景及其优势:
1. 网络通信
高效的数据传输:protobuf 使用紧凑的二进制格式,减少了数据传输的大小,提高了网络传输效率。
跨平台兼容性:protobuf 支持多种编程语言,确保不同平台和系统之间的数据交换无缝进行。
减少带宽消耗:紧凑的二进制格式减少了带宽消耗,适用于移动网络和低带宽环境。
2. 数据存储
高效的存储:protobuf 的二进制格式比文本格式(如 JSON 或 XML)更紧凑,节省存储空间。
快速读写:由于数据结构紧凑且预编译,读写操作非常快速,适合频繁读写的场景。
3. 配置文件
结构化数据:protobuf 提供了结构化的数据定义方式,确保配置文件的格式正确且易于维护。
版本控制:protobuf 支持字段的添加和删除,而不会破坏现有数据,便于版本控制。
4. 日志记录
高效日志:protobuf 的紧凑格式使得日志文件更小,便于存储和传输。
结构化日志:结构化的日志数据便于解析和分析,提高日志处理效率。
5. 游戏开发
实时数据同步:protobuf 的高效序列化和反序列化特性使得游戏中的实时数据同步更加高效。
跨平台支持:protobuf 支持多种平台,确保游戏在不同平台上的数据一致性。
6. 微服务架构
服务间通信:protobuf 适用于微服务架构中的服务间通信,提供高效的数据交换。
数据一致性:通过定义统一的 .proto 文件,确保不同服务之间的数据格式一致。
7. 移动应用
减少数据传输:移动网络带宽有限,protobuf 的紧凑格式减少了数据传输量。
快速加载:高效的序列化和反序列化操作提高了应用的启动速度和响应时间。
8. 物联网(IoT)
低功耗:紧凑的数据格式减少了设备的能耗,适用于电池供电的物联网设备。
高效通信:高效的通信机制确保了设备之间的数据交换快速且可靠。
9. 大数据处理
高效数据处理:protobuf 的高效序列化和反序列化特性使得大数据处理更加高效。
结构化数据:结构化的数据格式便于数据处理和分析。
10. API 通信
标准化数据格式:通过定义统一的 .proto 文件,确保 API 请求和响应的数据格式一致。
高效传输:紧凑的二进制格式减少了传输时间,提高了 API 的性能。
1.1 Protocol Buffers (protobuf)
1.1.1 定义
由Google开发的一种语言中立、平台中立、可扩展的序列化结构数据的方式。
1.1.2 核心功能
提供了一种高效且紧凑的方式来序列化和反序列化数据。它使用.proto文件来定义消息格式,并通过protoc编译器生成代码。
1.1.3 版本
主要有proto2和proto3两个主要版本,proto4尚处于实验阶段。
1.2 protobuf-net
1.2.1 定义
protobuf-net 是一个用于 .NET 平台的 Protocol Buffers 实现库。
1.2.2 核心功能
提供了对 Protocol Buffers 格式的读写支持。
支持直接在 .NET 类上进行序列化和反序列化,而不需要生成中间代码(虽然也可以与 protoc 生成的代码一起使用)。
提供了更灵活的配置选项,例如通过属性或接口来控制序列化行为。
1.2.3 特点
性能优化:针对 .NET 进行了性能优化。
易用性:提供了简单易用的 API,可以直接在现有类上使用 [ProtoContract] 和 [ProtoMember] 等属性。
1.2.4 兼容性
与标准的 Protocol Buffers 兼容,可以在不同平台上互操作。
1.3 关系总结
1.3.1 协议兼容
protobuf-net 完全遵循 Protocol Buffers 的序列化格式规范,因此它可以与任何其他实现 Protocol Buffers 的库(如 Java、Python、C++ 等)进行互操作。
1.3.2 平台特定
protobuf-net 是专门为 .NET 平台设计的,提供了更好的集成和性能优化。
1.3.3 简化开发
protobuf-net 提供了更高级别的抽象,使得开发者可以在不生成额外代码的情况下直接序列化和反序列化对象,从而简化了开发流程。
2 代码示例
2.1 Protocol Buffers (protobuf) 示例
2.1.1 准备 .proto文件
在本地准备 .proto文件,以下作为示例
syntax = "proto3";
package flywave.udp.model;
// 通用心跳包结构
message Heartbeat {
// 时间戳,表示心跳包发送的时间
int64 timestamp = 1; // 时间戳,默认值为 0
// 心跳次数,表示该心跳包是第几次发送
int32 count = 2; // 次数,默认值为 0
// 会话ID,标识当前会话的唯一ID
string session_id = 3; // 会话ID
// 客户端ID,标识发送心跳包的客户端
string client_id = 4; // 客户端ID
// 服务器ID,标识接收心跳包的服务器
string server_id = 5; // 服务器ID
// 状态,记录客户端或服务器的状态信息
string status = 6; // 状态
// 负载信息,记录客户端或服务器的负载情况
string load_info = 7; // 负载信息
// 加密字段,用于验证心跳包的完整性和来源合法性
string checksum = 8; // 校验和
// 预留字段,防止未来字段编号冲突
reserved 9 to 15;
}
/*
// 示例心跳包
Heartbeat example_heartbeat = {
timestamp: 1672531200,
count: 10,
session_id: "session_1234567890",
client_id: "client_abc123",
server_id: "server_def456",
status: "online",
load_info: "cpu=75%, mem=80%",
checksum: "a1b2c3d4e5f6g7h8i9j0"
};
1. 时间戳 (timestamp)
用途:记录心跳包发送的时间,用于检测网络延迟和丢包情况。
示例值:1672531200(表示2023年1月1日00:00:00 UTC)
2. 心跳次数 (count)
用途:记录心跳包的发送次数,用于检测是否连续丢包或网络中断。
示例值:10(表示这是第10次发送心跳包)
3. 会话ID (session_id)
用途:标识当前会话的唯一ID,确保断线重连时能够恢复到正确的会话。
示例值:"session_1234567890"
4. 客户端ID (client_id)
用途:标识发送心跳包的客户端,用于区分不同的客户端。
示例值:"client_abc123"
5. 服务器ID (server_id)
用途:标识接收心跳包的服务器,用于区分不同的服务器。
示例值:"server_def456"
6. 状态 (status)
用途:记录客户端或服务器的状态信息,如在线、离线、忙碌等。
示例值:
"online"
"offline"
"busy"
"idle"
7. 负载信息 (load_info)
用途:记录客户端或服务器的负载情况,如CPU使用率、内存使用率等。
示例值:
"cpu=75%, mem=80%"
"cpu=30%, mem=50%"
"cpu=90%, mem=95%"
8. 加密字段 (checksum)
用途:用于验证心跳包的完整性和来源合法性。
示例值:"a1b2c3d4e5f6g7h8i9j0"
示例心跳包
*/
2.1.2 导入proto插件
在VS-NuGet下载最新版本即可,导入时,选择.netstandard 2.0文件夹下的插件即可,这里需要下载插件和工具,工具对应可以生成对应语言代码。
google.protobuf 放到Plugins文件夹
google.protobuf.tools 也放到Plugins文件夹
依赖项
导入后插件会有一个依赖项(System.Runtime.CompilerServices.Unsafe),搜索下载安装即可。
2.1.2 protoc程序生成
使用相应平台的protoc程序生成对应代码,以下使用Unity + C# + Win64平台作为生成示例
2.1.2.1 编写 ProtoGen 脚本
用于生成csharp脚本,代码如下:
脚本需要放到Editor文件夹下
using System;
using System.Diagnostics;
using System.IO;
using UnityEditor;
using UnityEngine;
using Debug = UnityEngine.Debug;
// 用于生成protobuf类的MonoBehavior类
public class ProtoGen : MonoBehaviour
{
// 为不同平台和协议文件生成protobuf类的方法
[MenuItem("Tools/Generate HeartBeat (Windows x64)")]
public static void GenerateHeartBeat_Win_x64()
{
GenerateProtobufClasses("Plugins/google.protobuf/HeartBeat.proto", "windows_x64");
}
[MenuItem("Tools/Generate SimData (Windows x64)")]
public static void GenerateSimData_Win_x64()
{
GenerateProtobufClasses("Plugins/google.protobuf/SimData.proto", "windows_x64");
}
[MenuItem("Tools/Generate HeartBeat (Windows x86)")]
public static void GenerateHeartBeat_Win_x86()
{
GenerateProtobufClasses("Plugins/google.protobuf/HeartBeat.proto", "windows_x86");
}
[MenuItem("Tools/Generate SimData (Windows x86)")]
public static void GenerateSimData_Win_x86()
{
GenerateProtobufClasses("Plugins/google.protobuf/SimData.proto", "windows_x86");
}
[MenuItem("Tools/Generate HeartBeat (macOS x64)")]
public static void GenerateHeartBeat_Mac_x64()
{
GenerateProtobufClasses("Plugins/google.protobuf/HeartBeat.proto", "macosx_64");
}
[MenuItem("Tools/Generate SimData (macOS x64)")]
public static void GenerateSimData_Mac_x64()
{
GenerateProtobufClasses("Plugins/google.protobuf/SimData.proto", "macosx_64");
}
[MenuItem("Tools/Generate HeartBeat (Linux x64)")]
public static void GenerateHeartBeat_Linux_x64()
{
GenerateProtobufClasses("Plugins/google.protobuf/HeartBeat.proto", "linux_64");
}
[MenuItem("Tools/Generate SimData (Linux x64)")]
public static void GenerateSimData_Linux_x64()
{
GenerateProtobufClasses("Plugins/google.protobuf/SimData.proto", "linux_64");
}
[MenuItem("Tools/Generate HeartBeat (Linux x86)")]
public static void GenerateHeartBeat_Linux_x86()
{
GenerateProtobufClasses("Plugins/google.protobuf/HeartBeat.proto", "linux_x86");
}
[MenuItem("Tools/Generate SimData (Linux x86)")]
public static void GenerateSimData_Linux_x86()
{
GenerateProtobufClasses("Plugins/google.protobuf/SimData.proto", "linux_x86");
}
// 根据指定的协议文件和平台生成protobuf类的私有方法
private static void GenerateProtobufClasses(string protoFileName, string platform)
{
try
{
// 构建协议文件的完整路径
string protoFilePath = Path.Combine(Application.dataPath, protoFileName);
// 构建输出目录的完整路径
string outputDir = Path.Combine(Application.dataPath, "Plugins/google.protobuf");
// 构建protoc可执行文件的完整路径
string protocFileName = platform.StartsWith("windows") ? "protoc.exe" : "protoc";
string protocPath = Path.Combine(Application.dataPath, $"Plugins/google.protobuf.tools/3.29.3/tools/{
platform}/{
protocFileName}");
// 构建协议目录的完整路径
string protoDir = Path.Combine(Application.dataPath, "Plugins/google.protobuf");
// 校验路径合法性
if (!ValidatePaths(protoFilePath, protocPath))
{
return;
}
// 确保输出目录存在
Directory.CreateDirectory(outputDir);
// 设置启动protoc进程的信息
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = protocPath,
Arguments = $"--proto_path={
protoDir} --csharp_out={
outputDir} {
protoFilePath}",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
// 启动并等待protoc进程完成
using (Process process = new Process {
StartInfo = startInfo })
{
process.Start();
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
process.WaitForExit();
// 根据退出码输出成功或错误信息
if (process.ExitCode == 0)
{
Debug.Log("Protobuf classes generated successfully.");
}
else
{
Debug.LogError($"Failed to generate Protobuf classes. Output: {
output}, Error: {
error}");
}
}
}
catch (Exception ex