網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
目錄:
- 一、簡(jiǎn)單介紹DotnetCore3.0如何將.proto文件生成對(duì)應(yīng)的服務(wù)端和客戶端類
- 二、介紹如何在服務(wù)端使用Grpc,以及Grpc需要的條件(HTTP2、TLS)
- 三、介紹如何創(chuàng)建GrpcClient,以及Grpc通訊的四種模式
- 四、舉例如何使用Grpc
一、如何使用protobuf生成服務(wù)類
Grpc中使用協(xié)議緩沖區(qū) (protobuf) 用作接口設(shè)計(jì)語(yǔ)言 (IDL),它的主要內(nèi)容包含:
- GRPC 服務(wù)的定義。
- 客戶端和服務(wù)器之間發(fā)送的消息。
Grpc.Tools 這個(gè)工具,在每次編譯的時(shí)候,都能將.proto文件生成為對(duì)于的cs文件。 服務(wù)端和客戶端都需要添加。Grpc.AspNetCore這個(gè)包會(huì)對(duì)Grpc.Tools 進(jìn)行使用。引用了這個(gè)包之后。還有注意在Server和Client,都要在對(duì)應(yīng).csproj下面,修改GrpcServices這個(gè)配置的值,如果是服務(wù)端就寫Server,如果是客戶端就寫Client。
<ItemGroup> <Protobuf Include="Protos\xxxx.proto" GrpcServices="Server" /> </ItemGroup> <ItemGroup> <Protobuf Include="Protos\xxxx.proto" GrpcServices="Client" /> </ItemGroup>
二、服務(wù)端使用Grpc
1.添加引用
需要添加 Grpc.AspNetCore 的引用
2.配置Grpc
在Startup.cs中需要配置如下信息:
①ConfigureServices 中需要配置
services.AddGrpc();
②Configure 中需要配置endpoints
app.UseEndpoints(endpoints =>
{
// Communication with gRPC endpoints must be made through a gRPC client.
// To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909
endpoints.MapGrpcService<GreeterService>();
});
3.Kestrel的配置
Grpc中的endpoints需要下面的支持:
HTTP/2
gRPC 要求 HTTP/2。gRPC for ASP.NET Core 驗(yàn)證HttpRequest為HTTP/2。在大多數(shù)現(xiàn)代操作系統(tǒng)上,Kestrel支持 HTTP/2。默認(rèn)情況下,Kestrel 終結(jié)點(diǎn)配置為支持 HTTP/1.1 和 HTTP/2 連接。
傳輸安全性Transport Layer Security (TLS).
用于 gRPC 的 Kestrel 終結(jié)點(diǎn)應(yīng)使用 TLS 進(jìn)行保護(hù)。
在開(kāi)發(fā)版中,將在存在 ASP.NET Core 開(kāi)發(fā)證書https://localhost:5001時(shí),自動(dòng)創(chuàng)建一個(gè)使用 TLS 保護(hù)的終結(jié)點(diǎn)。不需要配置。https前綴驗(yàn)證 Kestrel 終結(jié)點(diǎn)是否正在使用 TLS。
在生產(chǎn)環(huán)境如果使用TLS中,必須顯式配置 TLS。
兩種方式配置對(duì)應(yīng)的TLS:
1.在AppSettings下面添加配置節(jié)點(diǎn):
{ "Kestrel": { "Endpoints": { "HttpsInlineCertFile": { "Url": "https://localhost:5001", "Protocols": "Http2", "Certificate": { "Path": "<path to .pfx file>", "Password": "<certificate password>" } } } } }
2.直接在代碼中添加
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
options.Listen(IPAddress.Any, 5001, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
listenOptions.UseHttps("<path to .pfx file>",
"<certificate password>");
});
});
webBuilder.UseStartup<Startup>();
});
此外TLS不僅僅適用于Client和Server間的安全傳輸,還可以用于服務(wù)協(xié)商。
我在看官網(wǎng)的關(guān)于服務(wù)協(xié)商時(shí)候,有些發(fā)懵,因?yàn)橛终f(shuō)了grpc 需要Http/2 、又需要TLS,但是后面又說(shuō)在不配置TLS的endpoint的時(shí)候...... 那么到底是需要TLS還是不需要TLS,Grpc到底是僅僅支持HTTP2還是會(huì)兼容HTTP1?
首先要明確幾個(gè)概念:
- 什么是EndPoint
- 什么是Grpc endpoint
- 什么是TLS
TLS 和 HTTP1、HTTP2
以下是我的理解:
Endpoint是一個(gè)大概念,不僅僅是grpc有endpoint,以前我們用的webservice、wcf都有,他可以是HTTP1的,也可以是HTTP2的,也可以都支持。僅僅是當(dāng)我們要用Grpc的時(shí)候我們需要使用HTTP2協(xié)議。
TLS是一種安全協(xié)議,是在傳輸層上的安全協(xié)議,具體是什么樣的可以不用了解,只是在.Net Core 中配置TLS,不僅僅作用于安全傳輸,還有作用于協(xié)議的選擇,當(dāng)我們的endpoint使用的是HTTP2,且不用TLS的時(shí)候,我們需要配置我們的Kestrel服務(wù)器的ListenOptions.Protocols 必須設(shè)置為HttpProtocols.Http2 ,換句話說(shuō) 如果ListenOptions.Protocols= HttpProtocols.Http1AndHttp2,那么就不能使用TLS。
總結(jié)一下就是:
你配置你的Kestrel 為使用HTTP2協(xié)議的時(shí)候,你可以使用TLS作為安全傳輸,也可以不用
你配置你的Kestrel 為使用兼容HTTP1協(xié)議的時(shí)候,那么你就不能用TLS
那么對(duì)于GPRC來(lái)講,他的endpoint 必須使用HTTP2協(xié)議,TLS也不是必須要配置的。
4.將Grpc像Api一樣提供出去
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
});
}
}
- ①繼承一下生成出來(lái)的抽象類 Greeter.GreeterBase
- ②根據(jù)自己的需要,依賴注入一些必要的Service類
- ③實(shí)現(xiàn)生成出來(lái)的Virtual方法
備注:
- 1.發(fā)生的問(wèn)題
- 2.Status(StatusCode=Internal, Detail="Error starting gRPC call: The SSL connection could not be established, see inner exception.")
https://docs.microsoft.com/zh-cn/aspnet/core/grpc/troubleshoot?view=aspnetcore-3.0
- 3.Grpc.Core.RpcException:“Status(StatusCode=Internal, Detail="Bad gRPC response. Response protocol downgraded to HTTP/1.0.")”
先按照下面的做
https://github.com/grpc/grpc-dotnet/issues/654
如果不行的話,檢查一下是否本地一直在開(kāi)著抓包工具之類的代理軟件,我之前一直不行,是因?yàn)楸镜剡\(yùn)行著Fiddler
三、客戶端使用Grpc
1.創(chuàng)建Gprc客戶端
和上面不一樣,grpc client 是通過(guò) xxx.proto 文件生成的具體的類型。里面包含了我們要調(diào)用的所有的方法。一般情況下,我們都是創(chuàng)建一個(gè)Channel類,然后通過(guò)Channel類在創(chuàng)建一個(gè)Client。
如下圖所示。
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greet.GreeterClient(channel);
ForAddress方法:
注:
gRPC的Channel維持了一個(gè)到遠(yuǎn)程服務(wù)的長(zhǎng)連接。
客戶端對(duì)象可以重用相同的通道。
與調(diào)用遠(yuǎn)程方法相比,創(chuàng)建通道是一項(xiàng)昂貴的操作,因此通常應(yīng)該為盡可能多的調(diào)用重用單個(gè)通道。
2.Grpc方法調(diào)用
Grpc具有多種不同的方式:
- ? Unary? (一元模式)
- ? Server streaming (服務(wù)器流)
- ? Client streaming (客戶端流)
- ? Bi-directional streaming (雙向流)
① Unary方式
從客戶端發(fā)送請(qǐng)求到服務(wù)端開(kāi)始,服務(wù)端響應(yīng)請(qǐng)求回來(lái)結(jié)束。每一個(gè)Unary 服務(wù)都會(huì)從*.proto文件中生成兩個(gè)具體的調(diào)用方法,一個(gè)異步方法,一個(gè)同步方法。例如下面的代碼:
var reply1 = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" });
var reply2 = client.SayHello(new HelloRequest { Name = "GreeterClient" });
②Server streaming 方式
從客戶端發(fā)送請(qǐng)求到服務(wù)端開(kāi)始,利用ResponseStream.MoveNext()方法讀取服務(wù)端返回的數(shù)據(jù),知道ResponseStream.MoveNext() 返回false說(shuō)明讀取結(jié)束。
var client = new Greet.GreeterClient(channel);
using (var call = client.SayHellos(new HelloRequest { Name = "World" }))
{
while (await call.ResponseStream.MoveNext())
{
Console.WriteLine("Greeting: " + call.ResponseStream.Current.Message);
// "Greeting: Hello World" is written multiple times
}
}
//或者C# 8以上通過(guò)await foreach來(lái)處理返回信息
using (var call = client.SayHellos(new HelloRequest { Name = "World" }))
{
await foreach (var response in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine("Greeting: " + response.Message);
// "Greeting: Hello World" is written multiple times
}
}
③Client streaming 方式
客戶端流調(diào)用不需要在客戶端發(fā)送請(qǐng)求之后開(kāi)始,客戶端可以選擇發(fā)送帶有RequestStream.WriteAsync的發(fā)送消息,當(dāng)客戶端完成發(fā)送消息請(qǐng)求流時(shí)。調(diào)用CompleteAsync來(lái)通知服務(wù),當(dāng)服務(wù)返回響應(yīng)消息時(shí),調(diào)用結(jié)束。
var client = new Counter.CounterClient(channel);
using (var call = client.AccumulateCount())
{
//調(diào)用三次
for (var i = 0; i < 3; i++)
{
await call.RequestStream.WriteAsync(new CounterRequest { Count = 1 });
}
//通知服務(wù)寫好了
await call.RequestStream.CompleteAsync();
//獲取返回結(jié)果
var response = await call;
Console.WriteLine($"Count: {response.Count}");
// Count: 3
}
④Bi-directional streaming call 方式
相當(dāng)于②和③的結(jié)合,也不需要等待客戶端發(fā)送完請(qǐng)求,使用RequestStream.WriteAsync寫入消息,通過(guò)ResponseStream.MoveNext()或者ResponseStream.ReadAllAsync()讀取服務(wù)端返回的數(shù)據(jù)。當(dāng)服務(wù)端不在返回的時(shí)候,結(jié)束請(qǐng)求。
using (var call = client.Echo())
{
Console.WriteLine("Starting background task to receive messages");
var readTask = Task.Run(async () =>
{
await foreach (var response in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine(response.Message);
// Echo messages sent to the service
}
});
Console.WriteLine("Starting to send messages");
Console.WriteLine("Type a message to echo then press enter.");
while (true)
{
var result = Console.ReadLine();
if (string.IsNullOrEmpty(result))
{
break;
}
await call.RequestStream.WriteAsync(new EchoMessage { Message = result });
}
Console.WriteLine("Disconnecting");
await call.RequestStream.CompleteAsync();
await readTask;
}
此外這里補(bǔ)充一下幾種方式適用的場(chǎng)景
- (1) 簡(jiǎn)單模式(Simple RPC)
這種模式最為傳統(tǒng),即客戶端發(fā)起一次請(qǐng)求,服務(wù)端響應(yīng)一個(gè)數(shù)據(jù),這和大家平時(shí)熟悉的RPC沒(méi)有什么大的區(qū)別,所以不再詳細(xì)介紹。
- (2) 服務(wù)端數(shù)據(jù)流模式(Server-side streaming RPC)
這種模式是客戶端發(fā)起一次請(qǐng)求,服務(wù)端返回一段連續(xù)的數(shù)據(jù)流。典型的例子是客戶端向服務(wù)端發(fā)送一個(gè)股票代碼,服務(wù)端就把該股票的實(shí)時(shí)數(shù)據(jù)源源不斷的返回給客戶端。
- (3) 客戶端數(shù)據(jù)流模式(Client-side streaming RPC)
與服務(wù)端數(shù)據(jù)流模式相反,這次是客戶端源源不斷的向服務(wù)端發(fā)送數(shù)據(jù)流,而在發(fā)送結(jié)束后,由服務(wù)端返回一個(gè)響應(yīng)。典型的例子是物聯(lián)網(wǎng)終端向服務(wù)器報(bào)送數(shù)據(jù)。
- (4) 雙向數(shù)據(jù)流模式(Bidirectional streaming RPC)
顧名思義,這是客戶端和服務(wù)端都可以向?qū)Ψ桨l(fā)送數(shù)據(jù)流,這個(gè)時(shí)候雙方的數(shù)據(jù)可以同時(shí)互相發(fā)送,也就是可以實(shí)現(xiàn)實(shí)時(shí)交互。典型的例子是聊天機(jī)器人。
四、舉例使用Grpc創(chuàng)建一個(gè)服務(wù)
1.創(chuàng)建兩個(gè)控制臺(tái)程序,分別為服務(wù)端和客戶端
2.在服務(wù)端創(chuàng)建服務(wù)
①在GrpcService中引入Grpc.AspNetCore包,此包會(huì)在編譯的時(shí)候,將.proto文件生成對(duì)應(yīng)的服務(wù)端grpc服務(wù)代碼。
.net core需要自己去生成,3.0以后已經(jīng)在上面的包中集成的生成的功能,只要生成代碼就會(huì)調(diào)用Grpc.Tools和Google.Protobuf去產(chǎn)生對(duì)應(yīng)的cs文件
②創(chuàng)建一個(gè)proto文件
syntax = "proto3";
option csharp_namespace = "GrpcService";
package Hello;
service Hello {
//通過(guò)一元方式傳輸
rpc SayHello (HelloRequest) returns (HelloReply);
//通過(guò)客戶端流的方式傳輸
rpc SayHelloClientStream (stream HelloRequest) returns (HelloReply);
//通過(guò)服務(wù)端流的方式傳輸
rpc SayHelloServerStream (HelloRequest) returns (stream HelloReply);
//通過(guò)雙向流的方式傳輸
rpc SayHelloStream (stream HelloRequest) returns (stream HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
并修改對(duì)應(yīng)的服務(wù)端csproj文件,將該proto文件加入到項(xiàng)目中,否則不會(huì)生成服務(wù)端代碼。
<ItemGroup> <Protobuf Include="Hello.proto" GrpcServices="Server" /> </ItemGroup>
③創(chuàng)建一個(gè)HelloService類,繼承自生成出來(lái)的抽象類XXXBase,然后可以重寫對(duì)應(yīng)我們聲明的方法,以便于提供客戶端訪問(wèn)。
注:xxx.xxxBase 可能在繼承的時(shí)候顯示不存在,那是因?yàn)闆](méi)有②之后沒(méi)有進(jìn)行編譯,編譯之后就能生成了。
public class HelloService : Hello.HelloBase
{
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Message = "Response:" + request.Name
});
}
public override async Task<HelloReply> SayHelloClientStream(IAsyncStreamReader<HelloRequest> requestStream, ServerCallContext context)
{
var current = new HelloRequest();
while (await requestStream.MoveNext())
{
current = requestStream.Current;
}
var task = new Task<HelloReply>(() =>
{
var reply = new HelloReply()
{
Message = "Response:" + current.Name
};
return reply;
});
task.Start();
var result = await task;
return result;
}
public override async Task SayHelloServerStream(HelloRequest request, IServerStreamWriter<HelloReply> responseStream, ServerCallContext context)
{
await responseStream.WriteAsync(new HelloReply() { Message = "Response:" + request.Name });
}
public override async Task SayHelloStream(IAsyncStreamReader<HelloRequest> requestStream,
IServerStreamWriter<HelloReply> responseStream,
ServerCallContext context)
{
while (await requestStream.MoveNext())
{
await responseStream.WriteAsync(new HelloReply() { Message = "Response:" + requestStream.Current.Name });
}
}
}
④因?yàn)槭欠?wù)端,一旦實(shí)現(xiàn)了所有的方法,還需要啟動(dòng)一個(gè)gRPC服務(wù)器,這樣客戶端才可以使用服務(wù)。
可以采用兩種方式來(lái)持久化服務(wù),監(jiān)聽(tīng)端口,處理請(qǐng)求:
a.使用Grpc.Core里面的Server (適用于.net core 3.0以下版本)
b.配置startup.cs中的ConfigureServices添加服務(wù),Configure方法中配置終結(jié)點(diǎn),并且配置對(duì)應(yīng)的Kestrel服務(wù)器
第一種方式:
class Program
{
private static Server _server;
static void Main(string[] args)
{
_server = new Server
{
Services = { Hello.BindService(new HelloService()) },
//這里使用的是不安全的方式
Ports = { new ServerPort("localhost", 50001, ServerCredentials.Insecure) }
};
_server.Start();
Console.WriteLine("Listen Port 50001");
Console.ReadKey();
_server?.ShutdownAsync().Wait();
}
}
第二種方式:
class Program
{
static void Main(string[] args)
{
#region 使用Kestrel服務(wù)器
CreateHostBuilder(args).Build().Run();
#endregion
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
// Setup a HTTP/2 endpoint without TLS.
options.ListenLocalhost(50001, o => o.Protocols =
HttpProtocols.Http2);
});
webBuilder.UseStartup<Startup>();
});
}
Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<HelloService>();
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client." +
" To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
});
});
}
}
3.在GrpcClient中引入如下包
①添加相關(guān)的依賴包
Install-Package Grpc.Net.Client
Install-Package Google.Protobuf
Install-Package Grpc.Tools
②添加之后,把服務(wù)端的proto文件一樣復(fù)制一份到客戶端,并在csproj下面追加如下的代碼,并編譯一次:
<ItemGroup> <Protobuf Include="Hello.proto" GrpcServices="Client" /> </ItemGroup>
③編寫客戶端調(diào)用的具體內(nèi)容
class Program
{
static async Task Main(string[] args)
{
// This switch must be set before creating the GrpcChannel/HttpClient.
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
var channel = GrpcChannel.ForAddress("http://localhost:50001");
var helloClient = new Hello.HelloClient(channel);
//一元調(diào)用(同步方法)
var reply = helloClient.SayHello(new HelloRequest { Name = "一元同步調(diào)用" });
Console.WriteLine($"{reply.Message}");
//一元調(diào)用(異步方法)
var reply2 = helloClient.SayHelloAsync(new HelloRequest { Name = "一元異步調(diào)用" }).GetAwaiter().GetResult();
Console.WriteLine($"{reply2.Message}");
//服務(wù)端流
var reply3 = helloClient.SayHelloServerStream(new HelloRequest { Name = "服務(wù)端流" });
while (await reply3.ResponseStream.MoveNext())
{
Console.WriteLine(reply3.ResponseStream.Current.Message);
}
//客戶端流
using (var call = helloClient.SayHelloClientStream())
{
await call.RequestStream.WriteAsync(new HelloRequest { Name = "客戶端流" + i.ToString() });
await call.RequestStream.CompleteAsync();
var reply4 = await call;
Console.WriteLine($"{reply4.Message}");
}
//雙向流
using (var call = helloClient.SayHelloStream())
{
Console.WriteLine("Starting background task to receive messages");
var readTask = Task.Run(async () =>
{
await foreach (var response in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine(response.Message);
}
});
for (var i = 0; i < 3; i++)
{
await call.RequestStream.WriteAsync(new HelloRequest { Name = "雙向流" + i.ToString()});
}
await call.RequestStream.CompleteAsync();
await readTask;
}
Console.ReadKey();
}
}
執(zhí)行:
這里要注意一段代碼AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true),如果不使用這段代碼,并且channel當(dāng)中還是http的話,那么就會(huì)出現(xiàn)下面的異常:
原文鏈接:https://www.cnblogs.com/dcz2015/p/11926587.html
相關(guān)推薦
- 2022-02-25 Linux下安裝Hadoop集群詳細(xì)步驟_Linux
- 2022-04-29 C#開(kāi)發(fā)Winform控件之打開(kāi)文件對(duì)話框OpenFileDialog類_C#教程
- 2021-11-27 Linux開(kāi)機(jī)自啟動(dòng)服務(wù)兩種方式介紹_Linux
- 2023-02-05 c++?梅森數(shù)源碼示例解析_C 語(yǔ)言
- 2022-10-17 Python可視化程序調(diào)用流程解析_python
- 2022-09-10 關(guān)于pandas.date_range()的用法及說(shuō)明_python
- 2021-12-06 linux下ceph分布式安裝使用教程_Linux
- 2023-11-26 (有效解決)Android Studio 運(yùn)行項(xiàng)目時(shí)報(bào) Package install error:
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支