C#网络协议第三方库Protobuf的使用详解
为什么要使用二进制数据通常我们写一个简单的网络通讯软件可能使用的最多的是字符串类型比较简单例如发送格式为(head)19|Msg:Heart|100,x,y,z…在接收端会解析收到的socket数据。这样通常是完全可行的但是随着数据量变大网络吞吐量就变大可能发送的字符串就不合适了可能会数据量变大。举例假如你要发送你的年收入和你的坐标例如你的年收入是一亿两千万123,456,789幸福死了你的坐标是1.234567如果通过字符串传输你的收入就是9位你的坐标可能你发小数因为精度问题还不准确通常使用二进制发送会大大节省。一个int32是4位float类型也是4位这样8位就够了。看下面的例子1234567891011121314staticvoidMain(string[] args){Console.WriteLine(Hello, World!);intmoney 123456789;floatx 1.234567f;byte[] moneybyte BitConverter.GetBytes(money);byte[] xbyte BitConverter.GetBytes(x);Console.WriteLine($moneybyte: {moneybyte.Length},{money} :xbyte: {xbyte.Length},{x});intmoneyget BitConverter.ToInt32(moneybyte);floatxget BitConverter.ToSingle(xbyte);Console.WriteLine($moneyget: {moneyget} :xget: {xget});}输出结果123Hello, World!moneybyte: 4,123456789 :xbyte: 4,1.234567moneyget: 123456789 :xget: 1.234567我们看到对于数字32位占4个字节这样如果是大量的数据就会很节省甚至你可以使用int16或者bool占用更小的字节。对于大量密集的网络程序使用二进制数据进行发送很必要的。初步思考如何方便的使用二进制或者封装是不是有这样的疑问如果要同步一个数据包含很多类型数据如何拼接和解析呢好像二进制没有字符串那么直观和好使用。比如我要同步的数据是如下数据通常我们把这种格式称作协议需要发送结构和解析正确的匹配才能解析。协议头|发送的大小|我的名字|18|123456789|1.234567|我的介绍|结束对于二进制如果我们有这样的结构123456789publicstructmydata{publicstringname;publicintage;publicintmoney;publicfloatx;publicstringreadme;}我们可以根据结构体内的属性进行二进制发送就可以了接收方也有这样的数据结构也进行解析就可以了这里要注意每个属性的顺序不能是错误的。网上有一些把结构体或者类打包成二进制的方法这里就不过多说明了。使用Protobufprotobuf就是专门为实现这个而生的从名字就可以看出来。Protobuf 的官方 C# 库是 Google.Protobuf可以通过 NuGet 包管理器来方便的使用。我们这里就来简单说一下如何使用安装首先vs里创建一个c#控制台程序。然后可以通过 NuGet安装第一个协议我们创建一个Person.proto文件1234567syntax proto3;message Person {stringname 1;int32 age 2;stringemail 3;}我们需要把这个proto转成c#可以解析的c#程序我们可以来到Protobuf库下载执行程序这个程序可以把proto解析成c#文件。我们下载好之后输入指令123F:\Downloads\protoc-29.3-win64\binprotoc --csharp_out. Person.protoF:\Downloads\protoc-29.3-win64\bin具体指令可以参考库里的文档–csharp_out输出cs文件 .是当前路径执行成功后会有一个Person.cs我们可以放入我们的项目这样就很容易解析协议了。生成的cs如下123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312// 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 codeusingpb global::Google.Protobuf;usingpbc global::Google.Protobuf.Collections;usingpbr global::Google.Protobuf.Reflection;usingscg global::System.Collections.Generic;/// summaryHolder for reflection information generated from test.proto/summarypublicstaticpartialclassTestReflection {#region Descriptor/// summaryFile descriptor for test.proto/summarypublicstaticpbr::FileDescriptor Descriptor {get{returndescriptor; }}privatestaticpbr::FileDescriptor descriptor;staticTestReflection() {byte[] descriptorData global::System.Convert.FromBase64String(string.Concat(Cgp0ZXN0LnByb3RvIjIKBlBlcnNvbhIMCgRuYW1lGAEgASgJEgsKA2FnZRgC,IAEoBRINCgVlbWFpbBgDIAEoCWIGcHJvdG8z));descriptor pbr::FileDescriptor.FromGeneratedCode(descriptorData,newpbr::FileDescriptor[] { },newpbr::GeneratedClrTypeInfo(null,null,newpbr::GeneratedClrTypeInfo[] {newpbr::GeneratedClrTypeInfo(typeof(global::Person), global::Person.Parser,new[]{Name,Age,Email},null,null,null,null)}));}#endregion}#region Messages[global::System.Diagnostics.DebuggerDisplayAttribute({ToString(),nq})]publicsealedpartialclassPerson : pb::IMessagePerson#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE, pb::IBufferMessage#endif{privatestaticreadonlypb::MessageParserPerson _parser newpb::MessageParserPerson(() newPerson());privatepb::UnknownFieldSet _unknownFields;[global::System.Diagnostics.DebuggerNonUserCodeAttribute][global::System.CodeDom.Compiler.GeneratedCode(protoc,null)]publicstaticpb::MessageParserPerson Parser {get{return_parser; } }[global::System.Diagnostics.DebuggerNonUserCodeAttribute][global::System.CodeDom.Compiler.GeneratedCode(protoc,null)]publicstaticpbr::MessageDescriptor Descriptor {get{returnglobal::TestReflection.Descriptor.MessageTypes[0]; }}[global::System.Diagnostics.DebuggerNonUserCodeAttribute][global::System.CodeDom.Compiler.GeneratedCode(protoc,null)]pbr::MessageDescriptor pb::IMessage.Descriptor {get{returnDescriptor; }}[global::System.Diagnostics.DebuggerNonUserCodeAttribute][global::System.CodeDom.Compiler.GeneratedCode(protoc,null)]publicPerson() {OnConstruction();}partialvoidOnConstruction();[global::System.Diagnostics.DebuggerNonUserCodeAttribute][global::System.CodeDom.Compiler.GeneratedCode(protoc,null)]publicPerson(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)]publicPerson Clone() {returnnewPerson(this);}/// summaryField number for the name field./summarypublicconstintNameFieldNumber 1;privatestringname_ ;[global::System.Diagnostics.DebuggerNonUserCodeAttribute][global::System.CodeDom.Compiler.GeneratedCode(protoc,null)]publicstringName {get{returnname_; }set{name_ pb::ProtoPreconditions.CheckNotNull(value,value);}}/// summaryField number for the age field./summarypublicconstintAgeFieldNumber 2;privateintage_;[global::System.Diagnostics.DebuggerNonUserCodeAttribute][global::System.CodeDom.Compiler.GeneratedCode(protoc,null)]publicintAge {get{returnage_; }set{age_ value;}}/// summaryField number for the email field./summarypublicconstintEmailFieldNumber 3;privatestringemail_ ;[global::System.Diagnostics.DebuggerNonUserCodeAttribute][global::System.CodeDom.Compiler.GeneratedCode(protoc,null)]publicstringEmail {get{returnemail_; }set{email_ pb::ProtoPreconditions.CheckNotNull(value,value);}}[global::System.Diagnostics.DebuggerNonUserCodeAttribute][global::System.CodeDom.Compiler.GeneratedCode(protoc,null)]publicoverrideboolEquals(objectother) {returnEquals(otherasPerson);}[global::System.Diagnostics.DebuggerNonUserCodeAttribute][global::System.CodeDom.Compiler.GeneratedCode(protoc,null)]publicboolEquals(Person other) {if(ReferenceEquals(other,null)) {returnfalse;}if(ReferenceEquals(other,this)) {returntrue;}if(Name ! other.Name)returnfalse;if(Age ! other.Age)returnfalse;if(Email ! other.Email)returnfalse;returnEquals(_unknownFields, other._unknownFields);}[global::System.Diagnostics.DebuggerNonUserCodeAttribute][global::System.CodeDom.Compiler.GeneratedCode(protoc,null)]publicoverrideintGetHashCode() {inthash 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();}returnhash;}[global::System.Diagnostics.DebuggerNonUserCodeAttribute][global::System.CodeDom.Compiler.GeneratedCode(protoc,null)]publicoverridestringToString() {returnpb::JsonFormatter.ToDiagnosticString(this);}[global::System.Diagnostics.DebuggerNonUserCodeAttribute][global::System.CodeDom.Compiler.GeneratedCode(protoc,null)]publicvoidWriteTo(pb::CodedOutputStream output) {#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODEoutput.WriteRawMessage(this);#elseif(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)]voidpb::IBufferMessage.InternalWriteTo(refpb::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(refoutput);}}#endif[global::System.Diagnostics.DebuggerNonUserCodeAttribute][global::System.CodeDom.Compiler.GeneratedCode(protoc,null)]publicintCalculateSize() {intsize 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();}returnsize;}[global::System.Diagnostics.DebuggerNonUserCodeAttribute][global::System.CodeDom.Compiler.GeneratedCode(protoc,null)]publicvoidMergeFrom(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)]publicvoidMergeFrom(pb::CodedInputStream input) {#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODEinput.ReadRawMessage(this);#elseuinttag;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;case10: {Name input.ReadString();break;}case16: {Age input.ReadInt32();break;}case26: {Email input.ReadString();break;}}}#endif}#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE[global::System.Diagnostics.DebuggerNonUserCodeAttribute][global::System.CodeDom.Compiler.GeneratedCode(protoc,null)]voidpb::IBufferMessage.InternalMergeFrom(refpb::ParseContext input) {uinttag;while((tag input.ReadTag()) ! 0) {if((tag 7) 4) {// Abort on any end group tag.return;}switch(tag) {default:_unknownFields pb::UnknownFieldSet.MergeFieldFrom(_unknownFields,refinput);break;case10: {Name input.ReadString();break;}case16: {Age input.ReadInt32();break;}case26: {Email input.ReadString();break;}}}}#endif}#endregion#endregion Designer generated code使用我们开始使用12345678910111213141516171819202122staticvoidMain(string[] args){Console.WriteLine(Hello, World!);var person newPerson{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进行解析。执行结果123Hello, World!serialsize 2 :Serialized Data: 10-12Deserialized Person: Name, Age18, Email我们看到二进制大小是2是因为使用了一种变长编码 (varint) 的优化方案可以看下官方的文档。通常短数据比较多使用变长编码的方式能够节省一些。