protobuf是一个语言无关、平台无关的序列化协议,由谷歌开源提供。再加上其高性能、存储占用更小等特点,在云原生的应用中越来越广泛。
在C#中主要有两种方法来使用protobuf协议,nuget包分别为Google.Protobuf
和protobuf-net
,其中Google.Protobuf
由谷歌官方提供。本文简要记录和展示Google.Protobuf
的使用方法和特点。
项目资料及文档
- 项目官网:https://developers.google.cn/protocol-buffers?hl=zh-cn
- github主页:https://github.com/protocolbuffers/protobuf/
- 官方文档:https://developers.google.cn/protocol-buffers/docs/overview?hl=zh-cn
- 该nuget包支持.NETFramework 4.5、.NETStandard1.1、.net5等
准备工作
需要用到的nuget有如下两个:Google.Protobuf
、Google.Protobuf.Tools
,其中Google.Protobuf
是主类库,运行时要用到。Google.Protobuf.Tools
提供了命令行工具,用于根据.proto文件转为目标语言的类型,仅开发时使用,运行时不需要。
本次Demo使用的.proto文件内容如下:
1
2
3
4
5
6
7
8
9
10
|
syntax = "proto3" ; option cc_enable_arenas = true ; package Tccc.Demo.Protobuf; message ErrorLog { string LogID = 1; string Context = 2; string Stack = 3; } |
首先需要根据.proto文件生成目标类型,操作如下:
1
|
./google.protobuf.tools\3.19.1\tools\windows_x64\protoc.exe --csharp_out=./generatedCode ./proto/ErrorLog.proto |
其中--csharp_out
选项是生成C#语言的目标类型,运行protoc.exe -h
查看帮助信息,可以看到还支持一下几种选项:
1
2
3
4
5
6
7
8
9
10
|
--proto_path=PATH --cpp_out=OUT_DIR Generate C++ header and source. --csharp_out=OUT_DIR Generate C# source file. --java_out=OUT_DIR Generate Java source file. --js_out=OUT_DIR Generate JavaScript source. --kotlin_out=OUT_DIR Generate Kotlin file. --objc_out=OUT_DIR Generate Objective-C header and source. --php_out=OUT_DIR Generate PHP source file. --python_out=OUT_DIR Generate Python source file. --ruby_out=OUT_DIR Generate Ruby source file. |
运行上述命令,会根据指定的ErrorLog.proto
文件生成ErrorLog.cs文件,文件中就是C#类型ErrorLog
。生成的代码中会给此类型增加方法void WriteTo(CodedOutputStream output)
和只读属性Parser
,接下来进行序列化和反序列化的关键。
生成的ErrorLog类的完整代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
|
// <auto-generated> // Generated by the protocol buffer compiler. DO NOT EDIT! // source: ProtoFiles/ErrorLog.proto // </auto-generated> #pragma warning disable 1591, 0612, 3021 #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 Tccc.Demo.Protobuf { /// <summary>Holder for reflection information generated from ProtoFiles/ErrorLog.proto</summary> public static partial class ErrorLogReflection { #region Descriptor /// <summary>File descriptor for ProtoFiles/ErrorLog.proto</summary> public static pbr::FileDescriptor Descriptor { get { return descriptor; } } private static pbr::FileDescriptor descriptor; static ErrorLogReflection() { byte [] descriptorData = global::System.Convert.FromBase64String( string .Concat( "ChlQcm90b0ZpbGVzL0Vycm9yTG9nLnByb3RvEhJUY2NjLkRlbW8uUHJvdG9i" , "dWYiOQoIRXJyb3JMb2cSDQoFTG9nSUQYASABKAkSDwoHQ29udGV4dBgCIAEo" , "CRINCgVTdGFjaxgDIAEoCUID+AEBYgZwcm90bzM=" )); descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, new pbr::FileDescriptor[] { }, new pbr::GeneratedClrTypeInfo( null , null , new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo( typeof (global::Tccc.Demo.Protobuf.ErrorLog), global::Tccc.Demo.Protobuf.ErrorLog.Parser, new []{ "LogID" , "Context" , "Stack" }, null , null , null , null ) })); } #endregion } #region Messages public sealed partial class ErrorLog : pb::IMessage<ErrorLog> #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE , pb::IBufferMessage #endif { private static readonly pb::MessageParser<ErrorLog> _parser = new pb::MessageParser<ErrorLog>(() => new ErrorLog()); private pb::UnknownFieldSet _unknownFields; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode( "protoc" , null )] public static pb::MessageParser<ErrorLog> Parser { get { return _parser; } } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode( "protoc" , null )] public static pbr::MessageDescriptor Descriptor { get { return global::Tccc.Demo.Protobuf.ErrorLogReflection.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 ErrorLog() { OnConstruction(); } partial void OnConstruction(); [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode( "protoc" , null )] public ErrorLog(ErrorLog other) : this () { logID_ = other.logID_; context_ = other.context_; stack_ = other.stack_; _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode( "protoc" , null )] public ErrorLog Clone() { return new ErrorLog( this ); } /// <summary>Field number for the "LogID" field.</summary> public const int LogIDFieldNumber = 1; private string logID_ = "" ; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode( "protoc" , null )] public string LogID { get { return logID_; } set { logID_ = pb::ProtoPreconditions.CheckNotNull(value, "value" ); } } /// <summary>Field number for the "Context" field.</summary> public const int ContextFieldNumber = 2; private string context_ = "" ; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode( "protoc" , null )] public string Context { get { return context_; } set { context_ = pb::ProtoPreconditions.CheckNotNull(value, "value" ); } } /// <summary>Field number for the "Stack" field.</summary> public const int StackFieldNumber = 3; private string stack_ = "" ; [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode( "protoc" , null )] public string Stack { get { return stack_; } set { stack_ = 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 ErrorLog); } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode( "protoc" , null )] public bool Equals(ErrorLog other) { if (ReferenceEquals(other, null )) { return false ; } if (ReferenceEquals(other, this )) { return true ; } if (LogID != other.LogID) return false ; if (Context != other.Context) return false ; if (Stack != other.Stack) 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 (LogID.Length != 0) hash ^= LogID.GetHashCode(); if (Context.Length != 0) hash ^= Context.GetHashCode(); if (Stack.Length != 0) hash ^= Stack.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 (LogID.Length != 0) { output.WriteRawTag(10); output.WriteString(LogID); } if (Context.Length != 0) { output.WriteRawTag(18); output.WriteString(Context); } if (Stack.Length != 0) { output.WriteRawTag(26); output.WriteString(Stack); } 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 (LogID.Length != 0) { output.WriteRawTag(10); output.WriteString(LogID); } if (Context.Length != 0) { output.WriteRawTag(18); output.WriteString(Context); } if (Stack.Length != 0) { output.WriteRawTag(26); output.WriteString(Stack); } 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 (LogID.Length != 0) { size += 1 + pb::CodedOutputStream.ComputeStringSize(LogID); } if (Context.Length != 0) { size += 1 + pb::CodedOutputStream.ComputeStringSize(Context); } if (Stack.Length != 0) { size += 1 + pb::CodedOutputStream.ComputeStringSize(Stack); } if (_unknownFields != null ) { size += _unknownFields.CalculateSize(); } return size; } [global::System.Diagnostics.DebuggerNonUserCodeAttribute] [global::System.CodeDom.Compiler.GeneratedCode( "protoc" , null )] public void MergeFrom(ErrorLog other) { if (other == null ) { return ; } if (other.LogID.Length != 0) { LogID = other.LogID; } if (other.Context.Length != 0) { Context = other.Context; } if (other.Stack.Length != 0) { Stack = other.Stack; } _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) { switch (tag) { default : _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input); break ; case 10: { LogID = input.ReadString(); break ; } case 18: { Context = input.ReadString(); break ; } case 26: { Stack = 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) { switch (tag) { default : _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input); break ; case 10: { LogID = input.ReadString(); break ; } case 18: { Context = input.ReadString(); break ; } case 26: { Stack = input.ReadString(); break ; } } } } #endif } #endregion } #endregion Designer generated code |
序列化操作
1
2
3
4
5
6
7
8
|
public static byte [] Serialize(ErrorLog log) { using (MemoryStream output = new MemoryStream()) { log.WriteTo(output); return output.ToArray(); } } |
反序列化操作
1
|
ErrorLog desErrorLog= ErrorLog.Parser.ParseFrom(data); |
使用特点和理解
- protoc.exe是支持生成多语言类型,这对于跨语言的混合编程比较方便。
- 根据上述使用步骤可以看到,必须先使用工具protoc生成目标类型,才能调用序列化和反序列化方法,这有些不符合.net平台的编码习惯。
- 一堆自动生成的C#类在可维护性方面欠佳,当需要调整属性字段时,还要通过工具重新生成,较为麻烦。
到此这篇关于Google.Protobuf工具在C#中的使用方法就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://www.cnblogs.com/chen943354/p/15597270.html