通過以上案例我們發現,Http請求調用服務實例屬實過於麻煩。其實對於請求同一個服務,很多步驟都是相同的,例如:服務名,地址,httpClient 創建步驟等。 RPC的出現,就是為瞭解決這一問題。 RPC: 即我們常說的遠程過程調用,就是像調用本地方法一樣調用遠程方法,通信協議大多採用二進位方式。 ...
RPC的出現,就是為瞭解決這一問題。
RPC: 即我們常說的遠程過程調用,就是像調用本地方法一樣調用遠程方法,通信協議大多採用二進位方式。
常用的RPC框架有(標粗的是準備講解的):
-
gRPC
gRPC是一個現代的開源高性能遠程過程調用(RPC)框架,可以在任何環境中運行。它可以有效地連接數據中心內和跨數據中心的服務,支持負載均衡、跟蹤、健康檢查和身份驗證。它也適用於分散式計算,將設備、移動應用程式和瀏覽器連接到後端服務---這是官方給的說明
-
openFeign
簡化HttpClient調用過程,讓net core開發變得更簡單。
-
Thrift
Thrift是一種介面描述語言和二進位通訊協議,它被用來定義和創建跨語言的服務。它被當作一個遠程過程調用(RPC)框架來使用,是由Facebook為“大規模跨語言服務開發”而開發的。它通過一個代碼生成引擎聯合了一個軟體棧,來創建不同程度的、無縫的跨平臺高效服務,可以使用C#、C++、Cappuccino、Cocoa、Delphi、Erlang、Go、Haskell、Java、Node.js、OCaml、Perl、PHP、Python、Ruby和Smalltalk。
-
Shuttler.net
Shuttler.Net是一個高性能分散式框架,如果你在使用老去的remoting,webservices分散式架構,或在使用新生的wcf,那麼你也可以嘗試下Shuttler.Net。 如果你想開發自己的IM服務端和客戶端(或游戲大廳),你也可以使用Shuttler.Net,只需你制定報文協議即可,其他傳輸層Shuttler幫你搞定。Shuttler.Net核心組件Artery正如她的名字一樣:脈,基於Tcp的應用棧,可以幫你傳輸任何能量,使你想唱就唱。
主要功能點包括:
-
分散式RPC,目前支持Tcp和Http(類REST風格)雙通道(見Demo:TcpRpcTest和HttpRpcTest): 可以多個RpcServer端和多個RpcClient端,其中client通過HashingAlgorithm根據Key計算出server。
-
分散式緩存系統(Memcached),包括MemcachedServer和MemcachedClient(見Demo:MemcachedTest): 可以多個MemcachedServer端和多個MemcachedClient端,其中client通過HashingAlgorithm根據Key計算出server。
-
IM協議棧,使用Shuttler.Net的Artery組件可以輕鬆實現一個IMServer端和IMClient端(見Demo:IMTest): IMTest中實現IM的登錄密碼校驗,通訊協議自己定義即可,協議Demo見Shuttler_Artery_Protocol。
-
1. gRPC 詳細教程
gRPC 的主要優點是:
-
現代高性能輕量級 RPC 框架。
-
協定優先 API 開發,預設使用協議緩衝區,允許與語言無關的實現。
-
可用於多種語言的工具,以生成強類型伺服器和客戶端。
-
支持客戶端、伺服器和雙向流式處理調用。
-
使用 Protobuf 二進位序列化減少對網路的使用。
這些優點使 gRPC 適用於:
-
效率至關重要的輕量級微服務。
-
需要多種語言用於開發的 Polyglot 系統。
-
需要處理流式處理請求或響應的點對點實時服務。
警告
若要將 ASP.NET Core gRPC用於 Azure 應用服務或 IIS,則該 gRPC 具有額外的要求。 有關可以在何處使用 gRPC 的詳細信息,請參閱 .NET 支持的平臺上的 gRPC
1. 快速入門
本教程演示瞭如何創建 .NET Core gRPC客戶端和 ASP.NET Core gRPC 伺服器。 最後會生成與 gRPC Greeter 服務進行通信的 gRPC 客戶端。
在本教程中,你將瞭解:
-
創建 gRPC 伺服器。
-
創建 gRPC 客戶端。
-
使用 gRPC Greeter 服務測試 gRPC 客戶端。
創建gRPC服務
-
啟動 Visual Studio 2022 並選擇“創建新項目”。
-
在“創建新項目”對話框中,搜索
gRPC
。 選擇“ASP.NET Core gRPC 服務”,並選擇“下一步” 。 -
在“配置新項目”對話框中,為“項目名稱”輸入
GrpcGreeter
。 將項目命名為“GrpcGreeter”非常重要,這樣在複製和粘貼代碼時命名空間就會匹配。 -
選擇“下一頁”。
-
在“其他信息”對話框中,選擇“.NET 6.0 (長期支持)”,然後選擇“創建。
項目文件
GrpcGreeter 項目文件:
-
Protos/greet.proto
:在
.proto
文件中定義服務和消息 -
Services
文件夾:包含Greeter
服務的實現。 -
appSettings.json
:包含配置數據,如 Kestrel 使用的協議。 -
Program.cs,其中包含:
-
gRPC 服務的入口點。
-
配置應用行為的代碼。
-
創建gRPC 控制台客戶端
-
打開 Visual Studio 的第二個實例並選擇“創建新項目”。
-
在“創建新項目”對話框中,選擇“控制台應用程式”,然後選擇“下一步” 。
-
在“項目名稱”文本框中,輸入“GrpcGreeterClient”,然後選擇“下一步” 。
-
在“其他信息”對話框中,選擇“.NET 6.0 (長期支持)”,然後選擇“創建”。
需要安裝的包
Grpc.Net.Client 2.50.0 Google.Protobuf 3.22.0 Grpc.Tools 2.50.0
添加 proto文件
-
在 gRPC 客戶端項目中創建 Protos 文件夾。
-
從 gRPC Greeter 服務將 Protos\greet.proto 文件複製到 gRPC 客戶端項目中的 Protos 文件夾 。
-
將
greet.proto
文件中的命名空間更新為項目的命名空間:option csharp_namespace = "GrpcGreeterClient";
-
編輯
GrpcGreeterClient.csproj
項目文件: -
添加具有引用 greet.proto 文件的
<Protobuf>
元素的項組:<ItemGroup> <Protobuf Include="Protos\greet.proto" GrpcServices="Client" /> </ItemGroup>
創建 Greeter 客戶端
-
構建客戶端項目,用於在
GrpcGreeterClient
命名空間中創建類型。
GrpcGreeterClient
類型是由生成進程自動生成的。 工具包
GrpcGreeterClient\obj\Debug\[TARGET_FRAMEWORK]\Protos\Greet.cs
:用於填充、序列化和檢索請求和響應消息類型的協議緩衝區代碼。
GrpcGreeterClient\obj\Debug\[TARGET_FRAMEWORK]\Protos\GreetGrpc.cs
:包含生成的客戶端類。
-
使用以下代碼更新 gRPC 客戶端
Program.cs
文件。using System.Threading.Tasks; using Grpc.Net.Client; using GrpcGreeterClient; // 服務端的地址 using var channel = GrpcChannel.ForAddress("https://localhost:7042"); var client = new Greeter.GreeterClient(channel); var reply = await client.SayHelloAsync( new HelloRequest { Name = "GreeterClient" }); Console.WriteLine("Greeting: " + reply.Message); Console.WriteLine("Press any key to exit..."); Console.ReadKey();
本文中的代碼需要 ASP.NET Core HTTPS 開發證書來保護 gRPC 服務。 如果 .NET gRPC 客戶端失敗並顯示消息
The remote certificate is invalid according to the validation procedure.
或The SSL connection could not be established.
,則開發證書不受信任。 要解決此問題,請參閱
2. proto 文件
gRPC 使用協定優先方法進行 API 開發。 預設情況下,協議緩衝區 (protobuf) 用作介面定義語言 (IDL)。 .proto
文件包含:
-
gRPC 服務的定義。
-
在客戶端與伺服器之間發送的消息。
.proto 添加到 C# 應用
通過將 .proto
文件添加到 <Protobuf>
項組中,可將該文件包含在項目中:
<ItemGroup> <Protobuf Include="Protos\greet.proto" GrpcServices="Server" /> </ItemGroup>
預設情況下,<Protobuf>
引用將生成具體的客戶端和服務基類。 可使用引用元素的 GrpcServices
特性來限制 C# 資產生成。 有效 GrpcServices
選項如下:
Proto GrpcServices 選項
-
Both
(如果不存在,則為預設值) -
Server
-
Client
-
None
.proto 工具支持
需要工具包
-
在每次生成項目時按需生成。
-
不會添加到項目中或是簽入到源代碼管理中。
-
是包含在 obj 目錄中的生成工件。
伺服器和客戶端項目都需要此包。 Grpc.AspNetCore
元包中包含對 Grpc.Tools
的引用。 伺服器項目可以使用 Visual Studio 中的包管理器或通過將 <PackageReference>
添加到項目文件來添加 Grpc.AspNetCore
:
<PackageReference Include="Grpc.AspNetCore" Version="2.40.0" />
客戶端項目應直接引用 Grpc.Tools
以及使用 gRPC 客戶端所需的其他包。 運行時不需要工具包,因此依賴項標記為 PrivateAssets="All"
:
<PackageReference Include="Google.Protobuf" Version="3.18.0" /> <PackageReference Include="Grpc.Net.Client" Version="2.40.0" /> <PackageReference Include="Grpc.Tools" Version="2.40.0"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference>
生成的 C# 代碼
工具包會生成表示在所包含 .proto
文件中定義的消息的 C# 類型。
對於伺服器端資產,會生成抽象服務基類型。 基類型包含 .proto
文件中所含的所有 gRPC 調用的定義。 創建一個派生自此基類型併為 gRPC 調用實現邏輯的具體服務實現。 對於 greet.proto
(前面所述的示例),會生成一個包含虛擬 SayHello
方法的抽象 GreeterBase
類型。 具體實現 GreeterService
會替代該方法,並實現處理 gRPC 調用的邏輯。
public class GreeterService : Greeter.GreeterBase { private readonly ILogger<GreeterService> _logger; public GreeterService(ILogger<GreeterService> logger) { _logger = logger; } public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context) { return Task.FromResult(new HelloReply { Message = "Hello " + request.Name }); } }
對於客戶端資產,會生成一個具體客戶端類型。 .proto
文件中的 gRPC 調用會轉換為具體類型中的方法,可以進行調用。 對於 greet.proto
(前面所述的示例),會生成一個 GreeterClient
類型。 調用 GreeterClient.SayHelloAsync
以發起對伺服器的 gRPC 調用。
// 埠號必須與gRPC 服務的埠一致 using var channel = GrpcChannel.ForAddress("https://localhost:7042"); var client = new Greeter.GreeterClient(channel); var reply = await client.SayHelloAsync( new HelloRequest { Name = "GreeterClient" }); Console.WriteLine("Greeting: " + reply.Message); Console.WriteLine("Press any key to exit..."); Console.ReadKey();
預設情況下,會為 <Protobuf>
項組中包含的每個 .proto
文件都生成伺服器和客戶端資產。 若要確保伺服器項目中僅生成伺服器資產,請將 GrpcServices
屬性設置為 Server
。
<ItemGroup> <Protobuf Include="Protos\greet.proto" GrpcServices="Server" /> </ItemGroup>
同樣,該屬性在客戶端項目中設置為 Client
。
<ItemGroup> <Protobuf Include="Protos\greet.proto" GrpcServices="Client" /> </ItemGroup>
3. ProtoBuf Message
消息是 Protobuf 中的主要數據傳輸對象, 它們在概念上類似於 .NET 類.
syntax = "proto3"; option csharp_namespace = "Contoso.Messages"; message Person { int32 id = 1; string first_name = 2 // FirstName string last_name = 3; }
前面的消息定義將三個欄位指定為名稱/值對。 與 .NET 類型上的屬性類似,每個欄位都有名稱和類型。 欄位類型可以是 Protobuf 標量值類型(如 int32
),也可以是其他消息。
Protobuf 樣式命名風格 建議使用 underscore_separated_names
作為欄位名稱。 為 .NET 應用創建的新 Protobuf 消息應遵循 Protobuf 樣式準則。 .NET 工具會自動生成使用 .NET 命名標準的 .NET 類型。 例如,first_name
Protobuf 欄位生成 FirstName
.NET 屬性。
包括名稱,消息定義中的每個欄位都有一個唯一的編號。 消息序列化為 Protobuf 時,欄位編號用於標識欄位。 序列化一個小編號比序列化整個欄位名稱要快。 因為欄位編號標識欄位,所以在更改編號時務必小心。
生成應用時,Protobuf 工具將從 .proto
文件生成 .NET 類型。 Person
消息生成 .NET 類:
public class Person { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
標量值類型
Protobuf 支持一系列本機標量值類型。 下表列出了全部本機標量值類型及其等效 C# 類型:
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
會引發錯誤。
日期和時間
本機標量類型不提供與 .NET 的
下表顯示日期和時間類型:
.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
之間進行轉換的方法。
Meeting meeting = new Meeting() { Start = DateTime.Now.ToTimestamp(), Duration = Duration.FromTimeSpan(TimeSpan.FromDays(1)) }; DateTime startTime = meeting.Start.ToDateTime(); DateTimeOffset time = meeting.Start.ToDateTimeOffset(); TimeSpan? duration = meeting.Duration?.ToTimeSpan();
備註
Timestamp
類型適用於 UTC 時間。DateTimeOffset
值的偏移量始終為零,並且DateTime.Kind
屬性始終為DateTimeKind.Utc
。
可為 null 的類型
C# 的 Protobuf 代碼生成使用本機類型,如 int
表示 int32
。 因此這些值始終包括在內,不能為 null
。
對於需要顯式 null
的值(例如在 C# 代碼中使用 int?
),Protobuf 的“已知類型”包括編譯為可以為 null 的 C# 類型的包裝器。 若要使用它們,請將 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# 類型:
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 |
位元組
Protobuf 支持標量值類型為 bytes
的二進位有效負載。 C# 中生成的屬性使用 ByteString
作為屬性類型。
使用 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());
集合
列表
Protobuf 中,在欄位上使用 repeated
首碼關鍵字指定列表。 以下示例演示如何創建列表:
message Person { // ... repeated string roles = 8; }
在生成的代碼中,repeated
欄位由 Google.Protobuf.Collections.RepeatedField<T>
泛型類型表示。
public class Person { // ... public RepeatedField<string> Roles { get; } }
RepeatedField<T>
實現了
var person = new Person(); person.Roles.Add("user"); var roles = new [] { "admin", "manager" }; person.Roles.Add(roles); var list = roles.ToList();
字典
.NET
ProtoBuf複製
message Person { // ... map<string, string> attributes = 9; }
在生成的 .NET 代碼中,map
欄位由 Google.Protobuf.Collections.MapField<TKey, TValue>
泛型類型表示。 MapField<TKey, TValue>
實現了 IDictionary。 與 repeated
屬性一樣,map
屬性沒有公共 setter。 項應添加到現有集合中。
var person = new Person(); // 添加一項 person.Attributes["created_by"] = "James"; // 添加多項 var attributes = new Dictionary<string, string> { ["last_modified"] = DateTime.UtcNow.ToString() }; person.Attributes.Add(attributes);
4. 創建 gRPC 服務和方法
本文檔介紹如何以 C# 創建 gRPC 服務和方法。 包括:
-
如何在
.proto
文件中定義服務和方法。 -
使用 gRPC C# 工具生成的代碼。
-
實現 gRPC 服務和方法。
創建新的 gRPC 服務
設置appsetting.json
"Kestrel": { "EndpointDefaults": { "Protocols": "Http2" } }
或者Program.cs 中配置如下代碼:
// Gprc 需要 Http2.0 builder.WebHost.UseKestrel(p => { p.ConfigureEndpointDefaults(opt => { opt.Protocols = HttpProtocols.Http2; }); });
引用包:
Grpc.AspNetCore 2.50.0
服務和消息是在 .proto
文件中定義的。 然後,C# 工具從 .proto
文件生成代碼。 對於伺服器端資產,將為每個服務生成一個抽象基類型,同時為所有消息生成類。
以下 .proto
文件:
-
定義
Greeter
服務。 -
Greeter
服務定義SayHello
調用。 -
SayHello
發送HelloRequest
消息並接收HelloReply
消息
syntax = "proto3"; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply); } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }
C# 工具生成 C# GreeterBase
基類型:
public abstract partial class GreeterBase { public virtual Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context) { throw new RpcException(new Status(StatusCode.Unimplemented, "")); } } public class HelloRequest { public string Name { get; set; } } public class HelloReply { public string Message { get; set; } }
預設情況下,生成的 GreeterBase
不執行任何操作。 它的虛擬 SayHe