gRPC 簡介
gRPC(gRPC Remote Procedure Calls)是一個由 Google 開源的,跨語言的,高性能的遠程過程調用(RPC)框架。 gRPC 使客戶端和服務端應用程序可以透明地進行通信,並簡化了連接系統的構建。它使用 HTTP/2 作為通信協議,使用 Protocol Buffers 作為序列化協議。
Github:https://github.com/grpc/grpc
DotNet Core 官方示例:https://github.com/dotnet/AspNetCore.Docs/tree/master/aspnetcore/grpc
gRPC 的主要優點
- 現代高性能輕量級 RPC 框架。
- 約定優先的 API 開發,默認使用 Protocol Buffers 作為描述語言,允許與語言無關的實現。
- 可用於多種語言的工具,以生成強類型的服務器和客戶端。
- 支持雙向流式的請求和響應,對批量處理、低延時場景友好。
- 通過 Protocol Buffers 二進制序列化減少網絡使用。
- 使用 HTTP/2 進行傳輸
gRPC 適用的場景
- 高性能的輕量級微服務。
- 多語言混合開發的 Polyglot 系統。
- 需要處理流式處理請求或響應的點對點實時通信服務。
gRPC 不適用的場景
- 瀏覽器可訪問的 API:瀏覽器不完全支持 gRPC。雖然 gRPC-Web 可以提供瀏覽器支持,但是它有局限性,引入了服務器代理。
- 廣播實時通信:gRPC 支持通過流進行實時通信,但不存在向已注冊連接廣播消息的概念。
- 進程間通信:進程必須承載 HTTP/2 才能接受傳入的 gRPC 調用,對於 Windows,進程間通信管道是一種更快速的方法。
gRPC 支持的語言
目前 gRPC 已經實現了對主流語言的支持,以下語言在 gRPC 的 Github 中都提供了實現。
在 DotNet Core 中使用 gRPC
創建服務端
Visual Studio 2019 中已經集成了 gRPC 項目的模版,我們可以通過這個模版快速的創建一個基於 DotNet Core 的 gRPC 項目。
創建好的項目結構如下:
這時候項目不用做任何修改就可以運行了,那么這個項目和普通的 DotNet Core 項目有什么不同呢?
首先項目文件 GrpcService.csproj
中引入了 Grpc.AspNetCore
包。
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.27.0" />
</ItemGroup>
在 appsettings.json
文件中多出了一個 Kestrel 節點,配置 Protocols 使用 Http2 協議。
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http2"
}
}
服務端 GreeterService
類的實現如下:
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
});
}
}
服務端 Startup
類中注入了 gRPC 服務:
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GreeterService>();
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");
});
});
}
gRPC 工具會根據 proto
文件自動生成需要使用的類,生成的類會存放在項目的 obj\Debug\netcoreapp3.1
目錄下:
創建客戶端
客戶端項目需要手動的創建,創建方法也很簡單,直接在解決方案中添加一個新的項目即可,這里我創建了一個空的 Web 項目。
項目創建好了以后首先要把 proto
文件添加到項目中,這里需要用到 dotnet-grpc
這個工具。
在命令行下安裝 gRPC 工具:
dotnet tool install dotnet-grpc -g
安裝完以后從命令行進入 GrpcClient 項目的目錄后,添加服務端的 proto
文件到客戶端。
dotnet grpc add-file ..\GrpcService\Protos\greet.proto
也可以使用遠程路徑的 proto
文件:
dotnet grpc add-url
https://raw.githubusercontent.com/grpc/grpc/master/examples/protos/keyvaluestore.proto -o /Protos/keyvaluesrore.proto
導入 proto
文件以后,GrpcClient 項目文件中會增加如下代碼:
<ItemGroup>
<Protobuf Include="..\GrpcService\Protos\greet.proto"
Link="Protos\greet.proto" />
<Protobuf Include="XXXX/Protos/keyvaluesrore.proto"
Link="Protos\keyvaluesrore.proto">
<SourceUrl>https://raw.githubusercontent.com/grpc/grpc/master/examples/protos/keyvaluestore.proto</SourceUrl>
</Protobuf>
</ItemGroup>
dotnet-grpc 除了以上用法還支持以下命令,詳細用法可以查閱 Microsoft Docs。
- dotnet grpc add-file
- dotnet grpc add-url
- dotnet grpc remove
- dotnet grpc refresh
接下來修改 Startup
中的代碼,在 ConfigureServices(IServiceCollection services)
方法注入 gRPC 客戶端代碼:
services.AddGrpcClient<GreeterClient>(options => options.Address = new
Uri("https://localhost:5001"));
注:
Uri("https://localhost:5001")
中使用服務端的端口和地址。
在 Configure
方法中添加響應代碼:
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
GreeterClient client = context.RequestServices.GetService<GreeterClient>();
HelloRequest request = new HelloRequest();
request.Name = "Charles";
var reply = await client.SayHelloAsync(request);
await context.Response.WriteAsync(reply.Message);
});
});
如果不想使用注入的方式也可以直接調用:
var httpClientHandler = new HttpClientHandler();
var httpClient = new HttpClient(httpClientHandler);
var channel = GrpcChannel.ForAddress(Address);
var client = new GreeterClient(channel);
HelloRequest request = new HelloRequest
{
Name = "Charles"
};
var reply = await client.SayHelloAsync(request);
await context.Response.WriteAsync(reply.Message);
啟動項目后在客戶端可以看到輸出了服務端返回的內容:
Hello Charles
限制消息大小
消息大小限制是一種有助於防止 gRPC 消耗過多資源的機制。gRPC 使用每個消息的大小限制來管理傳入和傳出消息。 默認情況下,gRPC 將傳入消息限制為 4 MB。 傳出消息沒有限制。
在服務端上,可以使用 AddGrpc 為應用中的所有服務配置 gRPC 消息限制:
services.AddGrpc(options =>
{
options.MaxReceiveMessageSize = 1 * 1024 * 1024; // 1 MB
options.MaxSendMessageSize = 1 * 1024 * 1024; // 1 MB
});
調用不安全的 gRPC 服務:
若要使客戶端調用不安全的 gRPC 服務,需要修改客戶端的配置。
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
調用不受信任、無效證書調用 gRPC 服務
DotNet gRPC 客戶端要求服務具有受信任的證書,若要調用不受信任、無效證書調用 gRPC 服務,需要修改客戶端請求的代碼:
services.AddGrpcClient<GreeterClient>(options => options.Address = new Uri(Address)).
ConfigurePrimaryHttpMessageHandler(provider =>
{
var handler = new SocketsHttpHandler();
handler.SslOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true; // 允許不受信任、無效證書
return handler;
});
非注入方式:
var httpClientHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};
var httpClient = new HttpClient(httpClientHandler);
var channel = GrpcChannel.ForAddress(Address);
var client = new GreeterClient(channel);
HelloRequest request = new HelloRequest
{
Name = "Charles"
};
var reply = await client.SayHelloAsync(request);
身份驗證和授權
服務端
為服務端加入身份驗證也很簡單,首先需要為項目引入 Microsoft.AspNetCore.Authentication.JwtBearer
這個包。
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.27.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.0" />
</ItemGroup>
修改服務端 Startup
類的代碼,分別在 Configure
和 ConfigureServices
方法中加入如下代碼:
services.AddAuthorization(options =>
{
options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
{
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireClaim(ClaimTypes.Name);
});
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters =
new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateActor = false,
ValidateLifetime = true,
IssuerSigningKey = SecurityKey
};
});
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GreeterService>();
endpoints.MapGet("/getToken", context =>
{
return context.Response.WriteAsync(GenerateJwtToken(context.Request.Query["name"]));
});
});
生成 Token 的方法:
private string GenerateJwtToken(string name)
{
if (string.IsNullOrEmpty(name))
{
throw new InvalidOperationException("Name is not specified.");
}
var claims = new[] { new Claim(ClaimTypes.Name, name) };
var credentials = new SigningCredentials(SecurityKey, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken("JwtSecurityIssuer", "JwtSecurityClients", claims, expires: DateTime.Now.AddSeconds(60), signingCredentials: credentials);
return JwtTokenHandler.WriteToken(token);
}
客戶端
客戶端的實現邏輯是在請求服務端之前先從服務端獲取到 Token,在之后調用響應服務的時候將 Token 放入請求頭中傳入服務端,如果不傳入直接請求服務端會返回 401。
var httpClient = new HttpClient();
var httpRequest = new HttpRequestMessage
{
RequestUri = new Uri($"{Address}/getToken?name=Charles"),
Method = HttpMethod.Get,
Version = new Version(2, 0)
};
var tokenResponse = await httpClient.SendAsync(httpRequest);
tokenResponse.EnsureSuccessStatusCode();
var token = await tokenResponse.Content.ReadAsStringAsync();
Metadata headers = null;
if (token != null)
{
headers = new Metadata
{
{ "Authorization", $"Bearer {token}" }
};
}
var channel = GrpcChannel.ForAddress(Address);
var client = new GreeterClient(channel);
HelloRequest request = new HelloRequest
{
Name = "Charles"
};
var reply = await client.SayHelloAsync(request, headers);
總結
以上就是在 DotNet Core 中使用 gRPC 的常用方法了,想深入學習可以去查看 gRPC DotNet 項目的 Github,里面有很多實例可以參考:https://github.com/grpc/grpc-dotnet/tree/master/examples