最近需要使用.NET Core搞一个需要和服务器通信的项目。通信协议是protobuf定义的。
我想既然客户端和服务器都是.NET Core,协议都protobuf写好了,那干脆上个gRPC呗。
于是研究了一下。发现.NET Core的gRPC还挺好用的。
以下为我今天研究的内容:
协议库 建立一个 .NET Standard Library 项目,添加如下依赖项:
( .NET Standard Library 可以同时兼容 .NET Core 和 .NET Framework )
NuGet:
Google.Protobuf
Grpc
Grpc.Tools
为该项目添加一个源文件,后缀名为 .proto 。在该文件中描述所需的消息协议以及RPC的接口定义。
.proto 文件中的 package 字段定义了生成库的命名空间。
选中这个 .proto 文件,在 Visual Studio 的 属性 窗格中,为该文件选择编译属性:
Advanced 选项中, Build Action 为: Protobuf compiler
gRPC 选项中, gRPC Stub Classes 为: Client and Server
Protobuf 选项中, Class Access 为: Public ; Compile Protobuf 为: True
直接生成该项目即可得到编译的带有rpc和protobuf协议库的dll文件。
gRPC Server 服务器端需要实现gRPC协议中定义的接口。
新建一个 .NET Core 项目作为gRPC Server。为该项目添加引用上面的协议库项目,或者通过dll、私有NuGet引用该库。
1. 实现RPC接口。 对于 .proto 文件中定义的一个 service 协议块,应在该服务对应的gRPC Server上给出一个实现。
若 命名空间为GrpcTest
, service名称为DoSomething
。应实现一个class继承自 GrpcTest.DoSomething.DoSomethingBase
。
请使用IntelliSence快速生成接口中应重载的函数框架。具体的说,为每一个rpc调用都实现一个public override async Task<返回值消息类型> RPC调用名(参数类型 request, ServerCallContext context)
的函数。
然后在本项目中实现这些函数的具体逻辑。
2. 开启 gRPC 监听服务器。 简单的代码如下:
1 2 3 4 5 6 7 8 Grpc.Core.Server server = new Grpc.Core.Server{ Service = { GrpcTest.DoSomething.BindServices(new DoSomethingImpl()) }, Ports = { new ServerPort("0.0.0.0" , 30000 , ServerCredentials.Insecure) } };
然后只需
即可启动 gRPC 服务器。
但是注意server.Start()
会立即返回,请使用信号量或者其他IO中断让主线程挂起,否则主线程会迅速退出。
gRPC Client 客户端一侧在指定服务器地址和端口号后,像调用普通的函数一样调用rpc接口即可。
新建一个 .NET Core 项目作为gRPC Client。同样通过添加项目引用、添加dll或私有NuGET的方式添加上面协议库的引用。
1. 获得gRPC调用对象 在实例化gRPC服务对象的时候,需要为gRPC服务传入channel对象,该对象定义了gRPC Server的地址和端口号。
示例代码如下:
1 2 Grpc.Core.Channel channel = new Grpc.Core.Channel("127.0.0.1:30000" , ChannelCredentials.Insecure); var client = new DoSomething.DoSomethingClient(channel);
此时client对象即为可用的gRPC服务对象。
2. 进行gRPC调用 像进行本地函数调用一样进行gRPC调用。
每个rpc调用都会生成两个函数,一个原名,为同步版本,在结果返回之前调用函数不会返回。
另一个为异步版本,在原名后追加Async。发送的数据和收到的数据都会按照协议文件中的消息定义自动解析。
备注 代码部分如下
协议
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 syntax = "proto3"; package GrpcTest; service DoSomething { rpc Eat (PairNumber) returns (SystemInfo) {} rpc Cat (PairNumber) returns (PairNumber) {} } message PairNumber { int32 up = 1; int32 down = 2; } message SystemInfo { string message = 1; }
gRPC Server
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 using System;using System.Threading.Tasks;using Grpc.Core;namespace GrpcTest { public class DoSomethingImpl : DoSomething.DoSomethingBase { public override async Task<PairNumber> Cat (PairNumber request, ServerCallContext context ) { return await Task.Run(() => { var result1 = request.Up + request.Down; var result2 = request.Up * request.Down; return new PairNumber { Up = result1, Down = result2 }; }); } public override async Task<SystemInfo> Eat (PairNumber request, ServerCallContext context ) { return await Task.Run(() => { var result1 = request.Up + request.Down; var result2 = request.Up * request.Down; string result = $"Grpc test num1={request.Up} and num2={request.Down} , result1={result1} but result2={result2} . That's all." ; return new SystemInfo { Message = result }; }); } } class Program { static void Main (string [] args ) { Server server = new Server { Services = { DoSomething.BindService(new DoSomethingImpl()) }, Ports = { new ServerPort("0.0.0.0" , 36667 , ServerCredentials.Insecure) } }; server.Start(); Console.WriteLine("RouteGuide server listening on port 36667" ); Console.WriteLine("Press any key to stop the server..." ); Console.ReadKey(); server.ShutdownAsync().Wait(); } } }
gRPC Client
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 using Grpc.Core;using System;using System.Threading.Tasks;namespace GrpcTest { class Program { static async Task RunRpc () { Channel channel = new Channel("127.0.0.1:36667" , ChannelCredentials.Insecure); var client = new DoSomething.DoSomethingClient(channel); var pn = new PairNumber { Up = 16 , Down = 24 }; var res = await client.CatAsync(pn); Console.WriteLine("Server Response: " + res.ToString()); var res2 = await client.EatAsync(pn); Console.WriteLine("Server Response: " + res2.Message); await channel.ShutdownAsync(); } static void Main (string [] args ) { RunRpc().Wait(); } } }