什么是gRpc gRPC是Google开发的高性能开源RPC(远程过程调用)框架,因其构建高效、健壮的 API 而受到开发人员的广泛欢迎。本文将探讨如何在.NET Core中使用gRPC服务。
gRPC可促进分布式系统之间高效、稳健的通信。它基于远程过程调用 (RPC) 协议,其中程序可以使过程(子例程)在另一个地址空间(通常在另一台服务器上)执行,而无需程序员显式编码远程通信的详细信息。gRPC(Google Remote Procedure Calls)即:Google远程过程调用,使用HTTP/2协议传输二进制消息,依靠Protocol buffers(又名Protobuf)来定义端点之间的通信协议。 最常见的应用场景:
微服务框架下,多种语言服务之间的高效交互。 将手机服务、浏览器连接至后台 产生高效的客户端库 为什么需要gRpc gRPC的创建是为了满足对高性能、与语言无关且与平台无关的框架的需求,以构建高效的API和微服务。它旨在取代传统的REST API并克服其局限性。gRpc有如下的优点:
高效率:gRPC使用协议缓冲区(protobufs)作为其接口定义语言(IDL)。Protocol Buffers 是一种与语言无关的二进制序列化格式,紧凑且高效。与REST API中使用的JSON或XML相比,这可以实现更小的消息大小和更快的序列化/反序列化过程。 高性能:gRPC 支持多路复用,可以通过单个TCP连接同时发送多个请求。该特性显着提升了性能,尤其是在高延迟连接的场景下,非常适合微服务架构。 多语言兼容:gRPC 支持多种编程语言,可以轻松构建多语言系统,其中不同的服务可以用其他语言实现,但仍然可以无缝通信。 流式传输:gRPC 支持一元(单个请求/响应)和流式RPC。双向流允许客户端和服务器相互发送消息流,从而实现实时通信模式。 通用性:gRPC 使用Protocol Buffers来定义服务方法和消息类型。此定义可用于生成多种编程语言的客户端和服务器代码,确保类型安全并减少出错的机会。 gRpc关键的概念 服务定义:gRPC服务是使用 Protocol Buffers 定义的。文件.proto定义服务方法和服务中使用的消息类型。 RPC 方法:gRPC服务公开客户端可以调用的远程方法。这些方法在文件中定义.proto,可以是一元的或流式的。 Protocol Buffers (protobufs): Protocol Buffers用于序列化结构化数据。它们提供与语言无关的接口定义,允许您定义数据模型和服务方法。 流式传输:gRPC支持流式传输,使客户端和服务器能够发送和接收消息流,使其适合实时应用程序。 代码生成:协议缓冲区被编译以生成各种编程语言的客户端和服务器代码。生成的代码处理序列化/反序列化和网络通信,使开发人员可以轻松使用gRPC。 项目实战 Nuget包 Grpc.Tools:提供在 .NET 应用程序中使用 gRPC 的工具和实用程序。它包括用于 C# 代码生成的 Protocol Buffers 编译器 ( protoc) 插件。该插件对于从文件(协议缓冲区文件)生成 C# 客户端和服务器代码至关重要.proto,这些文件定义了 gRPC 服务方法和消息类型。 Microsoft.AspNetCore.Grpc.JsonTranscoding:为 gRPC 服务创建 RESTful JSON API。 安装命令:
1 2 dotnet add package grpc.tools dotnet add package microsoft.aspnetcore.grpc.jsontranscoding
定义.proto文件 在 gRPC 中定义服务涉及创建 Protocol Buffers ( .proto) 文件,我们在其中指定用于通信的服务方法和消息类型。Protocol Buffers 提供了一种与语言无关的方式来定义我们的 API,允许我们用多种编程语言生成客户端和服务器代码
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 syntax = "proto3" ; option csharp_namespace = "ToDoGrpc" ;package todoit;service ToDoIt {rpc CreateToDo(CreateToDoRequest) returns (CreateToDoResponse) {}rpc ReadToDo(ReadToDoRequest) returns (ReadToDoResponse) {}rpc ListToDo(GetAllRequest) returns (GetAllResponse) {}rpc UpdateToDo(UpdateToDoRequest) returns (UpdateToDoResponse) {}rpc DeleteToDo(DeleteToDoRequest) returns (DeleteToDoResponse) {}} message CreateToDoRequest { string title=1 ; string description=2 ; } message CreateToDoResponse { int32 id=1 ; } message ReadToDoRequest { int32 id =1 ; } message ReadToDoResponse { int32 id =1 ; string title=2 ; string description = 3 ; string to_do_status=4 ; } message GetAllRequest { } message GetAllResponse { repeated ReadToDoResponse to_do =1 ; } message UpdateToDoRequest { int32 id =1 ; string title=2 ; string description = 3 ; string to_do_status=4 ; } message UpdateToDoResponse { int32 id =1 ; } message DeleteToDoRequest { int32 id =1 ; } message DeleteToDoResponse { int32 id =1 ; }
todo.proto文件创建了增上改查RPC服务,每个request和response是消息类型,消息类型定义了可以在gRpc方法中发送和接收的数据结构。
构筑服务 在实现服务之前,必须把todo.proto文件中添加到csproj项目文件并构建项目。
构建项目后,将生成一个包含抽象类的文件,该抽象类具有proto文件中定义的方法。我们将从此类继承我们的服务并重写方法。
服务实现 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 public class ToDoService : ToDoIt.ToDoItBase { private readonly AppDBContext _dbContext; public ToDoService (AppDBContext dbContext ) { _dbContext = dbContext; } public override async Task<CreateToDoResponse> CreateToDo (CreateToDoRequest request, ServerCallContext serverCallContext ) { if (string .IsNullOrEmpty(request.Title) || string .IsNullOrEmpty(request.Description)) throw new RpcException(new Status(StatusCode.InvalidArgument, "Data Empty" )); var todo_item = new ToDoItem { Title = request.Title, Description = request.Description }; await _dbContext.AddAsync(todo_item); await _dbContext.SaveChangesAsync(); return await Task.FromResult(new CreateToDoResponse { Id = todo_item.Id }); } public override async Task<ReadToDoResponse> ReadToDo (ReadToDoRequest request, ServerCallContext serverCallContext ) { if (request.Id <= 0 ) throw new RpcException(new Status(StatusCode.InvalidArgument, "id error" )); var todo_item = await _dbContext.TodoItems.FirstOrDefaultAsync(a => a.Id == request.Id); if (todo_item == null ) throw new RpcException(new Status(StatusCode.NotFound, $"No Task with id {request.Id} " )); return await Task.FromResult(new ReadToDoResponse { Id = todo_item.Id, Title = todo_item.Title, Description = todo_item.Description, ToDoStatus = todo_item.TodoStatus }); } public override async Task<GetAllResponse> ListToDo (GetAllRequest request, ServerCallContext context ) { var response = new GetAllResponse(); var todo_items = await _dbContext.TodoItems.ToListAsync(); foreach (var todo in todo_items) { response.ToDo.Add(new ReadToDoResponse { Id = todo.Id, Title = todo.Title, Description = todo.Description, ToDoStatus = todo.TodoStatus }); } return await Task.FromResult(response); } public override async Task<UpdateToDoResponse> UpdateToDo (UpdateToDoRequest request, ServerCallContext context ) { if (request.Id <= 0 || string .IsNullOrEmpty(request.Title) || string .IsNullOrEmpty(request.Description)) throw new RpcException(new Status(StatusCode.InvalidArgument, "param error" )); var todo = await _dbContext.TodoItems.FirstOrDefaultAsync(a => a.Id == request.Id); if (todo == null ) throw new RpcException(new Status(StatusCode.NotFound, $"No Task with id {request.Id} " )); todo.Title = request.Title; todo.Description = request.Description; todo.TodoStatus = request.ToDoStatus; await _dbContext.SaveChangesAsync(); return await Task.FromResult(new UpdateToDoResponse { Id = todo.Id }); } public override async Task<DeleteToDoResponse> DeleteToDo (DeleteToDoRequest request, ServerCallContext context ) { if (request.Id <= 0 ) throw new RpcException(new Status(StatusCode.InvalidArgument, "id error" )); var todo = await _dbContext.TodoItems.FirstOrDefaultAsync(a => a.Id == request.Id); if (todo == null ) throw new RpcException(new Status(StatusCode.NotFound, $"No Task with id {request.Id} " )); _dbContext.Remove(todo); await _dbContext.SaveChangesAsync(); return await Task.FromResult(new DeleteToDoResponse { Id = todo.Id }); } }
注册服务 在应用程序中配置服务
1 app.MapGrpcService<ToDoService>();
测试服务 启动服务 启动Postman,新建gRpc请求
导入todo.proto文件
选择测试服务
测试结果
gRPC JSON转码 gRPC 有一个限制,即不是所有平台都可以使用它。 浏览器并不完全支持 HTTP/2,这使得 REST API 和 JSON 成为将数据引入浏览器应用的主要方式。 尽管 gRPC 带来了很多好处,REST API 和 JSON 在新式应用中仍发挥着重要作用。 构建 gRPC 和 JSON Web API 给应用开发增加了不必要的开销。
gRPC JSON 转码是为 gRPC 服务创建 RESTful JSON API 的 ASP.NET Core 的扩展。 配置转码后,应用可以使用熟悉的HTTP概念调用gRPC服务。
添加包引用:Microsoft.AspNetCore.Grpc.JsonTranscoding; 在 Program.cs 文件中,将builder.Services.AddGrpc(); 更改为builder.Services.AddGrpc().AddJsonTranscoding(); 在包含 .csproj 文件的项目目录中创建目录结构 /google/api; 将 google/api/http.proto 和 google/api/annotations.proto 文件添加到 /google/api 目录中; 用HTTP绑定和路由在todo.proto 文件中注释 gRPC方法 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 syntax = "proto3" ; option csharp_namespace = "ToDoGrpc" ;import "google/api/annotations.proto" ;package todoit;service ToDoIt {rpc CreateToDo(CreateToDoRequest) returns (CreateToDoResponse) { option (google.api.http) = { post: "/todo/v1" , body:"*" }; } rpc ReadToDo(ReadToDoRequest) returns (ReadToDoResponse) { option (google.api.http) = { get: "/todo/v1/{id}" }; } rpc ListToDo(GetAllRequest) returns (GetAllResponse) { option (google.api.http) = { get: "/todo/v1" }; } rpc UpdateToDo(UpdateToDoRequest) returns (UpdateToDoResponse) { option (google.api.http) = { put: "/todo/v1" , body: "*" }; } rpc DeleteToDo(DeleteToDoRequest) returns (DeleteToDoResponse) { option (google.api.http) = { delete: "/todo/v1/{id}" }; } } message CreateToDoRequest { string title=1 ; string description=2 ; } message CreateToDoResponse { int32 id=1 ; } message ReadToDoRequest { int32 id =1 ; } message ReadToDoResponse { int32 id =1 ; string title=2 ; string description = 3 ; string to_do_status=4 ; } message GetAllRequest { } message GetAllResponse { repeated ReadToDoResponse to_do =1 ; } message UpdateToDoRequest { int32 id =1 ; string title=2 ; string description = 3 ; string to_do_status=4 ; } message UpdateToDoResponse { int32 id =1 ; } message DeleteToDoRequest { int32 id =1 ; } message DeleteToDoResponse { int32 id =1 ; }
更新appsettings.json中的默认协议: 1 2 3 4 5 6 7 { "Kestrel" : { "EndpointDefaults" : { "Protocols" : "Http1AndHttp2" } } }
测试结果 参考链接 代码实现 ASP.NET Core中的gRPC JSON转码