C# 网络协议第三方库Protobuf的使用

news2025/1/23 14:22:10

为什么要使用二进制数据

通常我们写一个简单的网络通讯软件可能使用的最多的是字符串类型,比较简单,例如发送格式为(head)19|Msg:Heart|100,x,y,z…,在接收端会解析收到的socket数据。
这样通常是完全可行的,但是随着数据量变大,网络吞吐量就变大,可能发送的字符串就不合适了,可能会数据量变大。
举例:
假如你要发送你的年收入和你的坐标,例如你的年收入是一亿两千万(123,456,789)(幸福死了)你的坐标是1.234567,如果通过字符串传输,你的收入就是9位,你的坐标可能你发小数因为精度问题还不准确
通常使用二进制发送会大大节省。一个int32是4位,float类型也是4位,这样8位就够了。
看下面的例子:

        static void Main(string[] args)
        {
            Console.WriteLine("Hello, World!");

            int money = 123456789;
            float x = 1.234567f;
            byte[] moneybyte = BitConverter.GetBytes(money);
            byte[] xbyte = BitConverter.GetBytes(x);
            Console.WriteLine($"moneybyte: {moneybyte.Length},{money} :xbyte: {xbyte.Length},{x}" );

            int moneyget = BitConverter.ToInt32(moneybyte);
            float xget = BitConverter.ToSingle(xbyte);
            Console.WriteLine($"moneyget: {moneyget} :xget: {xget}");
		}

输出结果

Hello, World!
moneybyte: 4,123456789 :xbyte: 4,1.234567
moneyget: 123456789 :xget: 1.234567

我们看到对于数字32位占4个字节,这样如果是大量的数据就会很节省,甚至你可以使用int16,或者bool占用更小的字节。
对于大量密集的网络程序使用二进制数据进行发送很必要的。

初步思考如何方便的使用二进制或者封装

是不是有这样的疑问,如果要同步一个数据包含很多类型数据,如何拼接和解析呢,好像二进制没有字符串那么直观和好使用。
比如我要同步的数据是如下数据(通常我们把这种格式称作协议),需要发送结构和解析正确的匹配才能解析。
协议头|发送的大小|我的名字|18|123456789|1.234567|我的介绍|结束
对于二进制如果我们有这样的结构

public struct mydata
{
	public string name;
	public int age;
	public int money;
	public float x;
	public string readme;
	 
}

我们可以根据结构体内的属性进行二进制发送就可以了,接收方也有这样的数据结构也进行解析就可以了,这里要注意每个属性的顺序不能是错误的。
网上有一些把结构体或者类打包成二进制的方法,这里就不过多说明了。

使用Protobuf

protobuf就是专门为实现这个而生的,从名字就可以看出来。
Protobuf 的官方 C# 库是 Google.Protobuf,可以通过 NuGet 包管理器来方便的使用。
我们这里就来简单说一下如何使用:

安装

首先vs里创建一个c#控制台程序。
然后可以通过 NuGet安装
在这里插入图片描述

第一个协议

我们创建一个Person.proto文件

syntax = "proto3";

message Person {
  string name = 1;
  int32 age = 2;
  string email = 3;
}

我们需要把这个proto转成c#可以解析的c#程序
我们可以来到Protobuf库下载执行程序,这个程序可以把proto解析成c#文件。

我们下载好之后:输入指令

F:\Downloads\protoc-29.3-win64\bin>protoc --csharp_out=. Person.proto

F:\Downloads\protoc-29.3-win64\bin>

具体指令可以参考库里的文档
–csharp_out输出cs文件 .是当前路径
执行成功后会有一个Person.cs我们可以放入我们的项目,这样就很容易解析协议了。
生成的cs如下:

// <auto-generated>
//     Generated by the protocol buffer compiler.  DO NOT EDIT!
//     source: test.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;
/// <summary>Holder for reflection information generated from test.proto</summary>
public static partial class TestReflection {

  #region Descriptor
  /// <summary>File descriptor for test.proto</summary>
  public static pbr::FileDescriptor Descriptor {
    get { return descriptor; }
  }
  private static pbr::FileDescriptor descriptor;

  static TestReflection() {
    byte[] descriptorData = global::System.Convert.FromBase64String(
        string.Concat(
          "Cgp0ZXN0LnByb3RvIjIKBlBlcnNvbhIMCgRuYW1lGAEgASgJEgsKA2FnZRgC",
          "IAEoBRINCgVlbWFpbBgDIAEoCWIGcHJvdG8z"));
    descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
        new pbr::FileDescriptor[] { },
        new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
          new pbr::GeneratedClrTypeInfo(typeof(global::Person), global::Person.Parser, new[]{ "Name", "Age", "Email" }, null, null, null, null)
        }));
  }
  #endregion

}
#region Messages
[global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
public sealed partial class Person : pb::IMessage<Person>
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
    , pb::IBufferMessage
#endif
{
  private static readonly pb::MessageParser<Person> _parser = new pb::MessageParser<Person>(() => new Person());
  private pb::UnknownFieldSet _unknownFields;
  [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
  [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
  public static pb::MessageParser<Person> Parser { get { return _parser; } }

  [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
  [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
  public static pbr::MessageDescriptor Descriptor {
    get { return global::TestReflection.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 Person() {
    OnConstruction();
  }

  partial void OnConstruction();

  [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
  [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
  public Person(Person other) : this() {
    name_ = other.name_;
    age_ = other.age_;
    email_ = other.email_;
    _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
  }

  [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
  [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
  public Person Clone() {
    return new Person(this);
  }

  /// <summary>Field number for the "name" field.</summary>
  public const int NameFieldNumber = 1;
  private string name_ = "";
  [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
  [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
  public string Name {
    get { return name_; }
    set {
      name_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
    }
  }

  /// <summary>Field number for the "age" field.</summary>
  public const int AgeFieldNumber = 2;
  private int age_;
  [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
  [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
  public int Age {
    get { return age_; }
    set {
      age_ = value;
    }
  }

  /// <summary>Field number for the "email" field.</summary>
  public const int EmailFieldNumber = 3;
  private string email_ = "";
  [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
  [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
  public string Email {
    get { return email_; }
    set {
      email_ = 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 Person);
  }

  [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
  [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
  public bool Equals(Person other) {
    if (ReferenceEquals(other, null)) {
      return false;
    }
    if (ReferenceEquals(other, this)) {
      return true;
    }
    if (Name != other.Name) return false;
    if (Age != other.Age) return false;
    if (Email != other.Email) 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 (Name.Length != 0) hash ^= Name.GetHashCode();
    if (Age != 0) hash ^= Age.GetHashCode();
    if (Email.Length != 0) hash ^= Email.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 (Name.Length != 0) {
      output.WriteRawTag(10);
      output.WriteString(Name);
    }
    if (Age != 0) {
      output.WriteRawTag(16);
      output.WriteInt32(Age);
    }
    if (Email.Length != 0) {
      output.WriteRawTag(26);
      output.WriteString(Email);
    }
    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 (Name.Length != 0) {
      output.WriteRawTag(10);
      output.WriteString(Name);
    }
    if (Age != 0) {
      output.WriteRawTag(16);
      output.WriteInt32(Age);
    }
    if (Email.Length != 0) {
      output.WriteRawTag(26);
      output.WriteString(Email);
    }
    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 (Name.Length != 0) {
      size += 1 + pb::CodedOutputStream.ComputeStringSize(Name);
    }
    if (Age != 0) {
      size += 1 + pb::CodedOutputStream.ComputeInt32Size(Age);
    }
    if (Email.Length != 0) {
      size += 1 + pb::CodedOutputStream.ComputeStringSize(Email);
    }
    if (_unknownFields != null) {
      size += _unknownFields.CalculateSize();
    }
    return size;
  }

  [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
  [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
  public void MergeFrom(Person other) {
    if (other == null) {
      return;
    }
    if (other.Name.Length != 0) {
      Name = other.Name;
    }
    if (other.Age != 0) {
      Age = other.Age;
    }
    if (other.Email.Length != 0) {
      Email = other.Email;
    }
    _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 10: {
          Name = input.ReadString();
          break;
        }
        case 16: {
          Age = input.ReadInt32();
          break;
        }
        case 26: {
          Email = 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 10: {
          Name = input.ReadString();
          break;
        }
        case 16: {
          Age = input.ReadInt32();
          break;
        }
        case 26: {
          Email = input.ReadString();
          break;
        }
      }
    }
  }
  #endif

}

#endregion


#endregion Designer generated code

使用

我们开始使用

static void Main(string[] args)
{
    Console.WriteLine("Hello, World!");


    var person = new Person
    {
        Age = 18,
        Name = "",
        Email = ""
    };
    byte[] serializedData = person.ToByteArray();

    Console.WriteLine($"serialsize {serializedData.Length} :Serialized Data: "  + BitConverter.ToString(serializedData));

    // 从字节数组反序列化
    var deserializedPerson = Person.Parser.ParseFrom(serializedData);
    Console.WriteLine($"Deserialized Person: Name={deserializedPerson.Name}, Age={deserializedPerson.Age}, Email={deserializedPerson.Email}");

}
代码中我们给person赋值,并通过ToByteArray二进制转化,得到二进制数组后就可以通过网络发送了。
当接收方收到这个二进制数据就可以通过ParseFrom进行解析。

执行结果

Hello, World!
serialsize 2 :Serialized Data: 10-12
Deserialized Person: Name=, Age=18, Email=

我们看到二进制大小是2是因为使用了一种变长编码 (varint) 的优化方案可以看下官方的文档。通常短数据比较多,使用变长编码的方式能够节省一些。

到这里就结束了。以上就是Protobuf的简单使用。

参考
https://github.com/protocolbuffers/protobuf

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2280939.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络安全 | 什么是正向代理和反向代理?

关注&#xff1a;CodingTechWork 引言 在现代网络架构中&#xff0c;代理服务器扮演着重要的角色。它们在客户端和服务器之间充当中介&#xff0c;帮助管理、保护和优化数据流。根据代理的工作方向和用途&#xff0c;代理服务器可分为正向代理和反向代理。本文将深入探讨这两种…

Python 预训练:打通视觉与大语言模型应用壁垒——Python预训练视觉和大语言模型

大语言模型是一种由包含数百亿甚至更多参数的深度神经网络构建的语言模型&#xff0c;通常使用自监督学习方法通过大量无标签文本进行训练&#xff0c;是深度学习之后的又一大人工智能技术革命。 大语言模型的发展主要经历了基础模型阶段(2018 年到2021年)、能力探索阶段(2019年…

AIGC视频生成模型:Meta的Emu Video模型

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍Meta的视频生成模型Emu Video&#xff0c;作为Meta发布的第二款视频生成模型&#xff0c;在视频生成领域发挥关键作用。 &#x1f33a;优质专栏回顾&am…

定位,用最通俗易懂的方法2.1:CRLB实例

二郎就不设置什么VIP可见啥的了&#xff0c;这样大家都能看到。 如果觉得受益&#xff0c;可以给予一些打赏&#xff0c;也算对原创的一些鼓励&#xff0c;谢谢。 钱的用途&#xff1a;1&#xff09;布施给他人&#xff1b;2&#xff09;二郎会有更多空闲时间写教程 起因&…

python学习笔记3-字符串常用的方法

一、判断&#xff08;9个&#xff09;&#xff1a; 二、查找和替换&#xff08;8个&#xff09; 三、⼤⼩写转换&#xff08;5个&#xff09; 四、⽂本对⻬&#xff08;3个&#xff09; 五、去除空⽩字符&#xff08;3个&#xff09; 六、拆分和连接 &#xff08;6个&#xff0…

【FreeRTOS 教程 一】任务结构体及其基础创建使用

目录 一、任务与协程的区别&#xff1a; &#xff08;1&#xff09;任务的特点&#xff1a; &#xff08;2&#xff09;协程的特点&#xff1a; &#xff08;3&#xff09;总结&#xff1a; 二、任务概述 &#xff1a; &#xff08;1&#xff09;任务状态&#xff1a; &…

node.js 07.npm下包慢的问题与nrm的使用

一.npm下包慢 因为npm i 默认从npm官网服务器进行下包,但是npm官网服务器是海外服务器所以响应很慢. 于是我们通过npm下包的时候通常用淘宝镜像进行下包,下面是切换到淘宝镜像地址下包的操作. 二.nrm的使用 nrm是一个管理切换npm下包地址的工具,可以快速切换下包的地址. 安…

2025发文新方向:AI+量化 人工智能与金融完美融合!

2025深度学习发论文&模型涨点之——AI量化 人工智能的融入&#xff0c;使量化交易实现了质的突破。借助机器学习、深度学习等先进技术&#xff0c;人工智能可高效处理并剖析海量市场数据&#xff0c;挖掘出数据背后错综复杂的模式与趋势&#xff0c;从而不仅提升了数据分析…

【16届蓝桥杯寒假刷题营】第1期DAY5

5.依依的询问最小值 - 蓝桥云课 问题描述 依依有个长度为 n 的序列 a&#xff0c;下标从 1 开始。 她有 m 次查询操作&#xff0c;每次她会查询下标区间在 [li​,ri​] 的 a 中元素和。她想知道你可以重新排序序列 a&#xff0c;使得这 m 次查询的总和最小。 求你求出 m 次…

25/1/15 嵌入式笔记 初学STM32F108

GPIO初始化函数 GPIO_Ini&#xff1a;初始化GPIO引脚的模式&#xff0c;速度和引脚号 GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化GPIOA的引脚0 GPIO输出控制函数 GPIO_SetBits&#xff1a;将指定的GPIO引脚设置为高电平 GPIO_SetBits(GPIOA, GPIO_Pin_0); // 将GPIO…

C语言--数据在内存中的存储

数据在内存中的存储 主要研究整型和浮点型在内存中的存储。 1. 整数在内存中的存储 在学习操作符的时候&#xff0c;就了解过了下面的内容&#xff1a; 整数的2进制表示方法有三种&#xff0c;即原码、反码和补码。 有符号的整数&#xff0c;三种表示方法均有符号位和数值…

DRG_DIP 2.0时代医院程序结构转型与数据结构优化研究

一、引言 1.1 DRG_DIP 2.0 改革背景与意义 医保支付方式改革在医疗保障制度改革中占据着极为关键的地位&#xff0c;是推动医疗领域变革的核心力量。它犹如一把精准的手术刀&#xff0c;对医疗资源的合理分配、医疗服务质量的稳步提升以及医疗费用的有效控制起着决定性作用。…

炸场硅谷,大模型“蒸汽机”迎来“瓦特时刻”

作者 | 曾响铃 文 | 响铃说 中国大模型又在包括硅谷在内的全球AI圈炸场了。 两天前&#xff0c;幻方量化旗下AI公司深度求索&#xff08;DeepSeek&#xff09;&#xff0c;以及月之暗面相隔20分钟相继发布了自家最新版推理模型&#xff0c;分别是DeepSeek-R1以及Kimi 全新多…

【自动驾驶】4 智驾生态概述

目录 1 智驾生态概述 ▲ 关键组成部分 ▲ 概述 2 关键技术 ▲ 传感器 ▲ 感知 ▲ 数据闭环 3 未来市场 1 智驾生态概述 智能驾驶生态&#xff0c;简称智驾生态&#xff0c;是指围绕智能驾驶技术的开发、应用、服务和支持所形成的产业体系和合作网络。 涵盖了从硬件设…

Excel 技巧14 - 如何批量删除表格中的空行(★)

本文讲如何批量删除表格中的空行。 1&#xff0c;如何批量删除表格中的空行 要点就是按下F5&#xff0c;然后选择空值条件以定位所有空行&#xff0c;然后删除即可。 按下F5 点 定位条件 选 空值&#xff0c;点确认 这样就选中了空行 然后点右键&#xff0c;选 删除 选中 下方…

C语言进阶习题【1】指针和数组(4)——指针笔试题3

笔试题5&#xff1a;下面代码输出是是什么&#xff1f; int main() {int a[5][5];int(*p)[4];p a;printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);return 0; }分析 代码结果 笔试题6&#xff1a;下面代码输出是是什么&#xff1…

5. 推荐算法的最基础和最直观的认识

1.性别年龄转换为统一的计量单位 所谓推荐&#xff0c;就是替别人推荐&#xff0c;比如工厂A需要招男员工&#xff0c;希望大家推荐认识的人。那么在这里&#xff0c;就有了推荐的概念&#xff0c;限定条件是男。我们知道&#xff0c;人的性别一般分为男或者女。在这里假设把男…

如何在Matplotlib中绘制多个Y轴刻度

Matplotlib是一个功能强大的Python库&#xff0c;在它的帮助下&#xff0c;我们可以绘制条形图&#xff0c;图表&#xff0c;绘图&#xff0c;比例等。在本文中&#xff0c;我们将尝试在Matplotlib中绘制多个Y轴刻度。 为什么多个Y轴刻度很重要&#xff1f; 绘制具有不同单位…

大模型GUI系列论文阅读 DAY1:《基于大型语言模型的图形用户界面智能体:综述》(6.6W 字长文)

摘要 图形用户界面&#xff08;Graphical User Interfaces, GUIs&#xff09;长期以来一直是人机交互的核心&#xff0c;为用户提供了直观且以视觉为驱动的方式来访问和操作数字系统。传统上&#xff0c;GUI交互的自动化依赖于基于脚本或规则的方法&#xff0c;这些方法在固定…

RabbitMQ1-消息队列

目录 MQ的相关概念 什么是MQ 为什么要用MQ MQ的分类 MQ的选择 RabbitMQ RabbitMQ的概念 四大核心概念 RabbitMQ的核心部分 各个名词介绍 MQ的相关概念 什么是MQ MQ(message queue)&#xff0c;从字面意思上看&#xff0c;本质是个队列&#xff0c;FIFO 先入先出&am…