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)
{
// 捕获并输出异常信息
Debug.LogError($"Failed to start protoc process. Exception: {ex.Message}");
}
}
// 校验协议文件和protoc可执行文件路径的方法
private static bool ValidatePaths(string protoFilePath, string protocPath)
{
// 检查协议文件是否存在
if (!File.Exists(protoFilePath))
{
Debug.LogError($"Proto file not found: {protoFilePath}");
return false;
}
// 检查protoc可执行文件是否存在
if (!File.Exists(protocPath))
{
Debug.LogError($"Protoc executable not found: {protocPath}");
return false;
}
// 路径合法,返回true
return true;
}
}
2.1.2.2 执行生成
在菜单栏执行 Tools/Generate HeartBeat (Windows x64),即可生成脚本到工程 - Plugins\google.protobuf 路径
生成的文件如下:
// <auto-generated>
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: HeartBeat.proto
// </auto-generated>
#pragma warning disable 1591, 0612, 3021, 8981
#region Designer generated code
using pb = global::Google.Protobuf;
using pbc = global::Google.Protobuf.Collections;
using pbr = global::Google.Protobuf.Reflection;
using scg = global::System.Collections.Generic;
namespace Flywave.Udp.Model {
/// <summary>Holder for reflection information generated from HeartBeat.proto</summary>
public static partial class HeartBeatReflection {
#region Descriptor
/// <summary>File descriptor for HeartBeat.proto</summary>
public static pbr::FileDescriptor Descriptor {
get { return descriptor; }
}
private static pbr::FileDescriptor descriptor;
static HeartBeatReflection() {
byte[] descriptorData = global::System.Convert.FromBase64String(
string.Concat(
"Cg9IZWFydEJlYXQucHJvdG8SEWZseXdhdmUudWRwLm1vZGVsIqIBCglIZWFy",
"dGJlYXQSEQoJdGltZXN0YW1wGAEgASgDEg0KBWNvdW50GAIgASgFEhIKCnNl",
"c3Npb25faWQYAyABKAkSEQoJY2xpZW50X2lkGAQgASgJEhEKCXNlcnZlcl9p",
"ZBgFIAEoCRIOCgZzdGF0dXMYBiABKAkSEQoJbG9hZF9pbmZvGAcgASgJEhAK",
"CGNoZWNrc3VtGAggASgJSgQICRAQYgZwcm90bzM="));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { },
new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
new pbr::GeneratedClrTypeInfo(typeof(global::Flywave.Udp.Model.Heartbeat), global::Flywave.Udp.Model.Heartbeat.Parser, new[]{ "Timestamp", "Count", "SessionId", "ClientId", "ServerId", "Status", "LoadInfo", "Checksum" }, null, null, null, null)
}));
}
#endregion
}
#region Messages
/// <summary>
/// 通用心跳包结构
/// </summary>
[global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
public sealed partial class Heartbeat : pb::IMessage<Heartbeat>
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
, pb::IBufferMessage
#endif
{
private static readonly pb::MessageParser<Heartbeat> _parser = new pb::MessageParser<Heartbeat>(() => new Heartbeat());
private pb::UnknownFieldSet _unknownFields;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public static pb::MessageParser<Heartbeat> Parser { get { return _parser; } }
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public static pbr::MessageDescriptor Descriptor {
get { return global::Flywave.Udp.Model.HeartBeatReflection.Descriptor.MessageTypes[0]; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
pbr::MessageDescriptor pb::IMessage.Descriptor {
get { return Descriptor; }
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public Heartbeat() {
OnConstruction();
}
partial void OnConstruction();
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public Heartbeat(Heartbeat other) : this() {
timestamp_ = other.timestamp_;
count_ = other.count_;
sessionId_ = other.sessionId_;
clientId_ = other.clientId_;
serverId_ = other.serverId_;
status_ = other.status_;
loadInfo_ = other.loadInfo_;
checksum_ = other.checksum_;
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public Heartbeat Clone() {
return new Heartbeat(this);
}
/// <summary>Field number for the "timestamp" field.</summary>
public const int TimestampFieldNumber = 1;
private long timestamp_;
/// <summary>
/// 时间戳,表示心跳包发送的时间
/// </summary>
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public long Timestamp {
get { return timestamp_; }
set {
timestamp_ = value;
}
}
/// <summary>Field number for the "count" field.</summary>
public const int CountFieldNumber = 2;
private int count_;
/// <summary>
/// 心跳次数,表示该心跳包是第几次发送
/// </summary>
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public int Count {
get { return count_; }
set {
count_ = value;
}
}
/// <summary>Field number for the "session_id" field.</summary>
public const int SessionIdFieldNumber = 3;
private string sessionId_ = "";
/// <summary>
/// 会话ID,标识当前会话的唯一ID
/// </summary>
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string SessionId {
get { return sessionId_; }
set {
sessionId_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
/// <summary>Field number for the "client_id" field.</summary>
public const int ClientIdFieldNumber = 4;
private string clientId_ = "";
/// <summary>
/// 客户端ID,标识发送心跳包的客户端
/// </summary>
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string ClientId {
get { return clientId_; }
set {
clientId_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
/// <summary>Field number for the "server_id" field.</summary>
public const int ServerIdFieldNumber = 5;
private string serverId_ = "";
/// <summary>
/// 服务器ID,标识接收心跳包的服务器
/// </summary>
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string ServerId {
get { return serverId_; }
set {
serverId_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
/// <summary>Field number for the "status" field.</summary>
public const int StatusFieldNumber = 6;
private string status_ = "";
/// <summary>
/// 状态,记录客户端或服务器的状态信息
/// </summary>
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string Status {
get { return status_; }
set {
status_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
/// <summary>Field number for the "load_info" field.</summary>
public const int LoadInfoFieldNumber = 7;
private string loadInfo_ = "";
/// <summary>
/// 负载信息,记录客户端或服务器的负载情况
/// </summary>
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string LoadInfo {
get { return loadInfo_; }
set {
loadInfo_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
/// <summary>Field number for the "checksum" field.</summary>
public const int ChecksumFieldNumber = 8;
private string checksum_ = "";
/// <summary>
/// 加密字段,用于验证心跳包的完整性和来源合法性
/// </summary>
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public string Checksum {
get { return checksum_; }
set {
checksum_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override bool Equals(object other) {
return Equals(other as Heartbeat);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public bool Equals(Heartbeat other) {
if (ReferenceEquals(other, null)) {
return false;
}
if (ReferenceEquals(other, this)) {
return true;
}
if (Timestamp != other.Timestamp) return false;
if (Count != other.Count) return false;
if (SessionId != other.SessionId) return false;
if (ClientId != other.ClientId) return false;
if (ServerId != other.ServerId) return false;
if (Status != other.Status) return false;
if (LoadInfo != other.LoadInfo) return false;
if (Checksum != other.Checksum) return false;
return Equals(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override int GetHashCode() {
int hash = 1;
if (Timestamp != 0L) hash ^= Timestamp.GetHashCode();
if (Count != 0) hash ^= Count.GetHashCode();
if (SessionId.Length != 0) hash ^= SessionId.GetHashCode();
if (ClientId.Length != 0) hash ^= ClientId.GetHashCode();
if (ServerId.Length != 0) hash ^= ServerId.GetHashCode();
if (Status.Length != 0) hash ^= Status.GetHashCode();
if (LoadInfo.Length != 0) hash ^= LoadInfo.GetHashCode();
if (Checksum.Length != 0) hash ^= Checksum.GetHashCode();
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
return hash;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override string ToString() {
return pb::JsonFormatter.ToDiagnosticString(this);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void WriteTo(pb::CodedOutputStream output) {
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
output.WriteRawMessage(this);
#else
if (Timestamp != 0L) {
output.WriteRawTag(8);
output.WriteInt64(Timestamp);
}
if (Count != 0) {
output.WriteRawTag(16);
output.WriteInt32(Count);
}
if (SessionId.Length != 0) {
output.WriteRawTag(26);
output.WriteString(SessionId);
}
if (ClientId.Length != 0) {
output.WriteRawTag(34);
output.WriteString(ClientId);
}
if (ServerId.Length != 0) {
output.WriteRawTag(42);
output.WriteString(ServerId);
}
if (Status.Length != 0) {
output.WriteRawTag(50);
output.WriteString(Status);
}
if (LoadInfo.Length != 0) {
output.WriteRawTag(58);
output.WriteString(LoadInfo);
}
if (Checksum.Length != 0) {
output.WriteRawTag(66);
output.WriteString(Checksum);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(output);
}
#endif
}
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
if (Timestamp != 0L) {
output.WriteRawTag(8);
output.WriteInt64(Timestamp);
}
if (Count != 0) {
output.WriteRawTag(16);
output.WriteInt32(Count);
}
if (SessionId.Length != 0) {
output.WriteRawTag(26);
output.WriteString(SessionId);
}
if (ClientId.Length != 0) {
output.WriteRawTag(34);
output.WriteString(ClientId);
}
if (ServerId.Length != 0) {
output.WriteRawTag(42);
output.WriteString(ServerId);
}
if (Status.Length != 0) {
output.WriteRawTag(50);
output.WriteString(Status);
}
if (LoadInfo.Length != 0) {
output.WriteRawTag(58);
output.WriteString(LoadInfo);
}
if (Checksum.Length != 0) {
output.WriteRawTag(66);
output.WriteString(Checksum);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(ref output);
}
}
#endif
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public int CalculateSize() {
int size = 0;
if (Timestamp != 0L) {
size += 1 + pb::CodedOutputStream.ComputeInt64Size(Timestamp);
}
if (Count != 0) {
size += 1 + pb::CodedOutputStream.ComputeInt32Size(Count);
}
if (SessionId.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(SessionId);
}
if (ClientId.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(ClientId);
}
if (ServerId.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(ServerId);
}
if (Status.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(Status);
}
if (LoadInfo.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(LoadInfo);
}
if (Checksum.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(Checksum);
}
if (_unknownFields != null) {
size += _unknownFields.CalculateSize();
}
return size;
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void MergeFrom(Heartbeat other) {
if (other == null) {
return;
}
if (other.Timestamp != 0L) {
Timestamp = other.Timestamp;
}
if (other.Count != 0) {
Count = other.Count;
}
if (other.SessionId.Length != 0) {
SessionId = other.SessionId;
}
if (other.ClientId.Length != 0) {
ClientId = other.ClientId;
}
if (other.ServerId.Length != 0) {
ServerId = other.ServerId;
}
if (other.Status.Length != 0) {
Status = other.Status;
}
if (other.LoadInfo.Length != 0) {
LoadInfo = other.LoadInfo;
}
if (other.Checksum.Length != 0) {
Checksum = other.Checksum;
}
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public void MergeFrom(pb::CodedInputStream input) {
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
input.ReadRawMessage(this);
#else
uint tag;
while ((tag = input.ReadTag()) != 0) {
if ((tag & 7) == 4) {
// Abort on any end group tag.
return;
}
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
break;
case 8: {
Timestamp = input.ReadInt64();
break;
}
case 16: {
Count = input.ReadInt32();
break;
}
case 26: {
SessionId = input.ReadString();
break;
}
case 34: {
ClientId = input.ReadString();
break;
}
case 42: {
ServerId = input.ReadString();
break;
}
case 50: {
Status = input.ReadString();
break;
}
case 58: {
LoadInfo = input.ReadString();
break;
}
case 66: {
Checksum = input.ReadString();
break;
}
}
}
#endif
}
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
uint tag;
while ((tag = input.ReadTag()) != 0) {
if ((tag & 7) == 4) {
// Abort on any end group tag.
return;
}
switch(tag) {
default:
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
break;
case 8: {
Timestamp = input.ReadInt64();
break;
}
case 16: {
Count = input.ReadInt32();
break;
}
case 26: {
SessionId = input.ReadString();
break;
}
case 34: {
ClientId = input.ReadString();
break;
}
case 42: {
ServerId = input.ReadString();
break;
}
case 50: {
Status = input.ReadString();
break;
}
case 58: {
LoadInfo = input.ReadString();
break;
}
case 66: {
Checksum = input.ReadString();
break;
}
}
}
}
#endif
}
#endregion
}
#endregion Designer generated code
2.1.2.3 生成的HeartBeat文件,执行效率很高的原因
2.1.2.3.1 高效的序列化和反序列化
Protocol Buffers(protobuf)是一种高效的二进制序列化格式。它使用紧凑的二进制格式来表示数据,这使得序列化和反序列化操作非常快速。
生成的 .cs 文件中包含了高度优化的代码来处理这些二进制数据,确保了高效的读写操作。
2.1.2.3.2 编译时生成的代码
protoc 编译器会根据 .proto 文件生成高度优化的 C# 代码。这些代码是专门为 protobuf 的二进制格式设计的,避免了运行时的额外开销。
生成的代码中包含了对字段的直接访问和操作,减少了方法调用的开销。
2.1.2.3.3 减少内存分配
生成的代码通常会尽量减少不必要的内存分配。例如,使用预分配的缓冲区来读取和写入数据,避免频繁的垃圾回收。
这些优化措施可以显著提高性能,特别是在处理大量数据时。
2.1.2.3.4 紧凑的数据表示
protobuf 使用可变长度编码(如 Varint)来表示整数,这使得数据在二进制格式中占用的空间更少。
更小的数据量意味着更快的 I/O 操作和更少的内存使用。
2.1.2.3.5 避免反射
生成的代码通常避免使用反射,而是直接访问字段。反射在 .NET 中是一个相对昂贵的操作,因为它涉及到运行时类型检查和方法调用。
直接访问字段可以显著提高性能。
2.1.2.3.6 预编译的代码
生成的 .cs 文件是预编译的,这意味着在运行时不需要动态生成代码或进行额外的解析。
这种预编译的方式确保了代码在运行时的高效执行。
2.1.2.3.7 具体示例
以下是一些具体的代码片段,展示了 protobuf 生成的代码是如何优化性能的:
a.直接字段访问:
public long Timestamp {
get { return timestamp_; }
set { timestamp_ = value; }
}
这种直接访问字段的方式避免了反射和额外的方法调用。
b. 高效的序列化:
public void WriteTo(pb::CodedOutputStream output) {
if (Timestamp != 0L) {
output.WriteRawTag(8);
output.WriteInt64(Timestamp);
}
if (Count != 0) {
output.WriteRawTag(16);
output.WriteInt32(Count);
}
// 其他字段的序列化
}
这种直接写入二进制数据的方式非常高效。
c. 高效的反序列化:
public void MergeFrom(pb::CodedInputStream input) {
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
case 8: {
Timestamp = input.ReadInt64();
break;
}
case 16: {
Count = input.ReadInt32();
break;
}
// 其他字段的反序列化
}
}
}
这种直接从二进制数据中读取字段的方式非常高效。
2.2 protobuf-net 示例
直接使用特性标注即可使用
using ProtoBuf;
[ProtoContract]
public class HeartBeat
{
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public string Message { get; set; }
}
// 序列化
HeartBeat heartbeat = new HeartBeat { Id = 1, Message = "Hello" };
using (var stream = File.Create("heartbeat.bin"))
{
Serializer.Serialize(stream, heartbeat);
}
// 反序列化
using (var stream = File.OpenRead("heartbeat.bin"))
{
HeartBeat deserializedHeartbeat = Serializer.Deserialize<HeartBeat>(stream);
}