gRPC和RESTful API是两种流行的服务通信方案,相比RESTful API,gRPC性能更强、开发效率更高、跨语言支持更完善,在许多场景下得到应用。
本文以一个实例作为切入点,对gRPC的整体情况进行一个简要介绍。
项目搭建
服务端:
创建ASP.NET Core gRPC
服务项目
创建proto文件,自动生成后台代码
创建服务(依赖自动生成的后台代码)
在主函数中对服务进行发布
客户端:
拷贝服务端的proto文件,自动生成客户端代码
在主函数中直接调用远程服务
小实例
proto文件
以下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 syntax = "proto3" ; option csharp_namespace = "GrpcService1" ;package greet;service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) ; rpc Compute (ComputeData) returns (ComputeResult) ; } message HelloRequest { string name = 1 ; } message HelloReply { string message = 1 ; } message ComputeData { string name = 1 ; int32 age = 2 ; repeated string hobbies = 3 ; map<string , int32 > scores = 4 ; Address address = 5 ; enum Gender { UNKNOWN = 0 ; MALE = 1 ; FEMALE = 2 ; } Gender gender = 7 ; } message ComputeResult { string res=1 ; } message Address { string city = 1 ; string street = 2 ; }
proto中的message类似C#中的class,可以包含普通数据类型、数组、字典、嵌套message和枚举
message的定义是使用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 public class GreeterService : Greeter.GreeterBase { private readonly ILogger<GreeterService> _logger; public GreeterService (ILogger<GreeterService> logger ) { _logger = logger; } public override Task<ComputeResult> Compute (ComputeData request, ServerCallContext context ) { StringBuilder sb = new StringBuilder(); sb.AppendLine($"姓名:{request.Name} " ); sb.AppendLine($"年龄:{request.Age} " ); sb.Append($"爱好:" ); foreach (var hobby in request.Hobbies) sb.Append($"{hobby} " ); sb.AppendLine(); sb.Append($"成绩:" ); foreach (var (key,value ) in request.Scores) sb.Append($"{key} -{value } " ); sb.AppendLine(); sb.AppendLine($"地址:{request.Address.City} ,{request.Address.Street} " ); sb.Append($"性别:{request.Gender.ToString()} " ); return Task.FromResult(new ComputeResult { Res = sb.ToString() }); } public override Task<HelloReply> SayHello (HelloRequest request, ServerCallContext context ) { return Task.FromResult(new HelloReply { Message = "Hello " + request.Name }); } }
主函数
在主函数中对上述服务类进行注册:
1 2 3 4 5 6 7 8 9 10 11 public static void Main (string [] args ){ var builder = WebApplication.CreateBuilder(args ); builder.Services.AddGrpc(); var app = builder.Build(); app.MapGrpcService<GreeterService>(); app.MapGet("/" , () => "Communication with gRPC endpoints must be made through a gRPC client." ); app.Run(); }
客户端代码
在客户端拷贝相同的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 using Grpc.Net.Client;using GrpcGreeterClient;var channel = GrpcChannel.ForAddress("https://localhost:7109" );var client = new Greeter.GreeterClient(channel);var reply = await client.SayHelloAsync(new HelloRequest { Name = "李浩" });var data = new ComputeData(){ Name = "李浩" , Age = 31 , Hobbies = { "跑步" , "看书" , "编程" }, Scores = { { "数学" , 140 }, { "语文" , 130 } }, Address = new Address { Street = "和平大道" , City = "武汉市" , }, Gender = ComputeData.Types.Gender.Male, }; var computeReply = await client.ComputeAsync(data);Console.WriteLine("Greeting: " + reply.Message); Console.WriteLine("Compute Result:" + computeReply.Res); Console.ReadKey();
proto生成代码探究
对于以下简单的proto文件:
1 2 3 4 5 6 7 8 9 10 11 12 syntax = "proto3" ; option csharp_namespace = "GrpcService1" ;package greet;service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) ; } message HelloRequest { string name = 1 ; } message HelloReply { string message = 1 ; }
在服务端会生成如下图所示的代码元素:
在客户端会生成如下图所示的代码元素:
使用proto文件的心智简化:
在服务端会自动生成一个抽象类(是嵌套类),其中定义了远程调用函数,用户需要定义新的服务类并具体实现该远程调用函数
在客户端会自动生成一个类,其中会包含远程调用函数的多版本实现,用户直接调用这些函数就可以与服务端通信
在proto文件中定义的message会转换成C#类,字段也会相应映射
我们在使用gRPC时,只需要牢记以上三点并熟练掌握proto编写语法即可