Protobuf
Protobuf 消息
消息定义中的每个字段都有一个唯一的编号。 消息序列化为 Protobuf 时,字段编号用于标识字段。 序列化一个小编号比序列化整个字段名称要快。
标量值类型
| Protobuf 类型 | C# 类型 |
|---|---|
double |
double |
float |
float |
int32 |
int |
int64 |
long |
uint32 |
uint |
uint64 |
ulong |
sint32 |
int |
sint64 |
long |
fixed32 |
uint |
fixed64 |
ulong |
sfixed32 |
int |
sfixed64 |
long |
bool |
bool |
string |
string |
bytes |
ByteString |
标量值始终具有默认值,并且该默认值不能设置为 null。 此约束包括 string 和 ByteString,它们都属于 C# 类。 string 默认为空字符串值,ByteString 默认为空字节值。 尝试将它们设置为 null 会引发错误。
对于需要显式 null 的值,请将 wrappers.proto 导入到 .proto 文件中,如以下代码所示:
syntax = "proto3" import "google/protobuf/wrappers.proto" message Person { // ... google.protobuf.Int32Value age = 5; }
wrappers.proto 类型不会在生成的属性中公开。 Protobuf 会自动将它们映射到 C# 消息中相应的可为 null 的 .NET 类型。 例如,google.protobuf.Int32Value 字段生成 int? 属性。 引用类型属性(如 string 和 ByteString )保持不变,但可以向它们分配 null,这不会引发错误。
| C# 类型 | 已知类型包装器 |
|---|---|
bool? |
google.protobuf.BoolValue |
double? |
google.protobuf.DoubleValue |
float? |
google.protobuf.FloatValue |
int? |
google.protobuf.Int32Value |
long? |
google.protobuf.Int64Value |
uint? |
google.protobuf.UInt32Value |
ulong? |
google.protobuf.UInt64Value |
string |
google.protobuf.StringValue |
ByteString |
google.protobuf.BytesValue |
字节
使用 ByteString.CopyFrom(byte[] data) 从字节数组创建新实例:
var data = await File.ReadAllBytesAsync(path); var payload = new PayloadResponse(); payload.Data = ByteString.CopyFrom(data);
使用 ByteString.Span 或 ByteString.Memory 直接访问 ByteString 数据。 或调用 ByteString.ToByteArray() 将实例转换回字节数组:
var payload = await client.GetPayload(new PayloadRequest()); await File.WriteAllBytesAsync(path, payload.Data.ToByteArray());
日期和时间
本机标量类型不提供与 .NET 的 DateTimeOffset、DateTime 和 TimeSpan 等效的日期和时间值。 可使用 Protobuf 的一些“已知类型”扩展来指定这些类型。
| .NET 类型 | Protobuf 已知类型 |
|---|---|
DateTimeOffset |
google.protobuf.Timestamp |
DateTime |
google.protobuf.Timestamp |
TimeSpan |
google.protobuf.Duration |
syntax = "proto3" import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; message Meeting { string subject = 1; google.protobuf.Timestamp start = 2; google.protobuf.Duration duration = 3; }
C# 类中生成的属性不是 .NET 日期和时间类型。 属性使用 Google.Protobuf.WellKnownTypes 命名空间中的 Timestamp 和 Duration 类。 这些类提供在 DateTimeOffset、DateTime 和 TimeSpan 之间进行转换的方法。
// Create Timestamp and Duration from .NET DateTimeOffset and TimeSpan. var meeting = new Meeting { Time = Timestamp.FromDateTimeOffset(meetingTime), // also FromDateTime() Duration = Duration.FromTimeSpan(meetingLength) }; // Convert Timestamp and Duration to .NET DateTimeOffset and TimeSpan. var time = meeting.Time.ToDateTimeOffset(); var duration = meeting.Duration?.ToTimeSpan();
Timestamp 类型适用于 UTC 时间。 DateTimeOffset 值的偏移量始终为零,并且 DateTime.Kind 属性始终为 DateTimeKind.Utc。
列表
Protobuf 中,在字段上使用 repeated 前缀关键字指定列表
message Person { // ... repeated string roles = 8; } public class Person { // ... public RepeatedField<string> Roles { get; } }
字典
.NET IDictionarymap 表示
message Person { // ... map<string, string> attributes = 9; }
在生成的 .NET 代码中,map 字段由 Google.Protobuf.Collections.MapField 泛型类型表示。
无结构的条件消息
Protobuf 是一种协定优先的消息传递格式。 构建应用时,必须在 .proto 文件中指定应用的消息,包括其字段和类型。 Protobuf 的协定优先设计非常适合强制执行消息内容,但可能会限制不需要严格协定的情况:
- 包含未知有效负载的消息。 例如,具有可以包含任何消息的字段的消息。
- 条件消息。 例如,从 gRPC 服务返回的消息可能是成功结果或错误结果。
- 动态值。 例如,具有包含非结构化值集合的字段的消息,类似于 JSON。
Protobuf 提供语言功能和类型来支持这些情况。
任意
利用 Any 类型,可以将消息作为嵌入类型使用,而无需 .proto 定义。 若要使用 Any 类型,请导入 any.proto。
import "google/protobuf/any.proto"; message Status { string message = 1; google.protobuf.Any detail = 2; } // Create a status with a Person message set to detail. var status = new ErrorStatus(); status.Detail = Any.Pack(new Person { FirstName = "James" }); // Read Person message from detail. if (status.Detail.Is(Person.Descriptor)) { var person = status.Detail.Unpack(); // ... }
Oneof
oneof 字段是一种语言特性。 编译器在生成消息类时处理 oneof 关键字。 使用 oneof 指定可能返回 Person 或 Error 的响应消息可能如下所示:
message Person { // ... } message Error { // ... } message ResponseMessage { oneof result { Error error = 1; Person person = 2; } }
在整个消息声明中,oneof 集内的字段必须具有唯一的字段编号。
使用 oneof 时,生成的 C# 代码包括一个枚举,用于指定哪些字段已设置。 可以测试枚举来查找已设置的字段。 未设置的字段将返回 null 或默认值,而不是引发异常。
var response = await client.GetPersonAsync(new RequestMessage()); switch (response.ResultCase) { case ResponseMessage.ResultOneofCase.Person: HandlePerson(response.Person); break; case ResponseMessage.ResultOneofCase.Error: HandleError(response.Error); break; default: throw new ArgumentException("Unexpected result."); }
“值”
Value 类型表示动态类型的值。 它可以是 null、数字、字符串、布尔值、值字典 (Struct) 或值列表 (ValueList)。 Value 是一个 Protobuf 已知类型,它使用前面讨论的 oneof 功能。 若要使用 Value 类型,请导入 struct.proto。
import "google/protobuf/struct.proto"; message Status { // ... google.protobuf.Value data = 3; } // Create dynamic values. var status = new Status(); status.Data = Value.FromStruct(new Struct { Fields = { ["enabled"] = Value.ForBoolean(true), ["metadata"] = Value.ForList( Value.FromString("value1"), Value.FromString("value2")) } }); // Read dynamic values. switch (status.Data.KindCase) { case Value.KindOneofCase.StructValue: foreach (var field in status.Data.StructValue.Fields) { // Read struct fields... } break; // ... }
直接使用 Value 可能很冗长。 使用 Value 的替代方法是通过 Protobuf 的内置支持,将消息映射到 JSON。 Protobuf 的 JsonFormatter 和 JsonWriter 类型可用于任何 Protobuf 消息。 Value 特别适用于与 JSON 进行转换。
以下是与之前的代码等效的 JSON:
// Create dynamic values from JSON. var status = new Status(); status.Data = Value.Parser.ParseJson(@"{ ""enabled"": true, ""metadata"": [ ""value1"", ""value2"" ] }"); // Convert dynamic values to JSON. // JSON can be read with a library like System.Text.Json or Newtonsoft.Json var json = JsonFormatter.Default.Format(status.Metadata); var document = JsonDocument.Parse(json);
dotnet-grpc
dotnet-grpc 是一种 .NET Core 全局工具,用于在 .NET gRPC 项目中管理 Protobuf (.proto) 引用。 该工具可以用于添加、刷新、删除和列出 Protobuf 引用。
dotnet tool install -g dotnet-grpc
dotnet-grpc 可以用于将 Protobuf 引用作为 项添加到 .csproj 文件
dotnet-grpc 工具可以:
- 从磁盘上的本地文件创建 Protobuf 引用。
- 从 URL 指定的远程文件创建 Protobuf 引用。
- 确保将正确的 gRPC 包依赖项添加到项目。
例如,将 Grpc.AspNetCore 包添加到 Web 应用。 Grpc.AspNetCore 包含 gRPC 服务器和客户端库以及工具支持。 或者,将 Grpc.Net.Client、Grpc.Tools 和 Google.Protobuf 包(其中仅包含 gRPC 客户端库和工具支持)添加到控制台应用。
参见
https://docs.microsoft.com/zh-cn/aspnet/core/grpc/dotnet-grpc?view=aspnetcore-3.1