参考:http://t.zoukankan.com/lhwpc-p-15157339.html
文档:http://doc.oschina.net/grpc?t=60132
https://docs.microsoft.com/zh-cn/aspnet/core/grpc/dotnet-grpc?view=aspnetcore-6.0
1、使用VS2019 创建 ASP.NET Core gRPC 服务 项目,名称暂时叫:GrpcService1
2、在Protos文件夹下,新增 协议缓冲区文件 student.proto,在这个文件上面右键属性:生成操作(Build Action)改为 ProtoBuf compiler,gRPC Stub Classes 改为 Server only,Class Access 改为 Public,Compile Protobuf 改为 是。
syntax = "proto3"; option csharp_namespace = "GrpcService1"; package student; //服务名:Student service Student{ //标准RPC rpc SayHello (StudentRequest) returns (StudentReply); //服务端流RPC rpc GetStudent (StudentRequest) returns (stream StudentReply); //客户端流RPC rpc AddStudent (stream StudentRequest) returns (StudentReply); //双流RPC rpc Examination (stream StudentRequest) returns (stream StudentReply); } //枚举,枚举类型的第一个选项的标识符必须是0,这也是枚举类型的默认值。 //别名(Alias),允许为不同的枚举值赋予相同的标识符,称之为别名,需要打开allow_alias选项。 enum Gender{ //option allow_alias = true; FEMALE = 0; MALE = 1; } //请求 message StudentRequest { string name = 1; int32 age = 2; Gender gender = 3; } //返回 message StudentReply { string name = 1; int32 age = 2; Gender gender = 3; string message = 4; }
3、安装dotnet-grpc.NET Core 全局工具,请运行以下命令:dotnet tool install -g dotnet-grpc
4、编辑GrpcService1.csproj文件,在原来的ItemGroup中新增配置student.proto,发现他其实会自动加的。
<ItemGroup> <Protobuf Include="Protos\greet.proto" GrpcServices="Server" /> <Protobuf Include="Protos\student.proto" GrpcServices="Server" /> </ItemGroup>
5、重新生成项目,在目录GrpcService1\obj\Debug\netcoreapp3.1下应该可以找到新生成的Student.cs 和 StudentGrpc.cs,成功了。
6、在Services文件夹下面就可以建服务实现类了StudentService.cs,至此服务端完成。
using AutoMapper; using Grpc.Core; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace GrpcService1 { public class StudentService : Student.StudentBase { private List<StudentReply> students = new List<StudentReply>() { new StudentReply(){ Name = "jay",Age=19, Gender = Gender.Male, Message="welcom" }, new StudentReply(){ Name = "robot",Age=13, Gender = Gender.Male, Message="hello" }, new StudentReply(){ Name = "blues",Age=16, Gender = Gender.Female, Message="hi" } }; private readonly ILogger<StudentService> _logger; public StudentService(ILogger<StudentService> logger) { _logger = logger; } /// <summary> /// 标准RPC /// </summary> /// <param name="request"></param> /// <param name="context"></param> /// <returns></returns> public override Task<StudentReply> SayHello(StudentRequest request, ServerCallContext context) { return Task.FromResult(new StudentReply { Message = "Hello " + request.Name }); } /// <summary> /// 服务器流RPC模式,服务器返回的数据写入 IServerStreamWriter /// 返回一个异步 Task,客户端在返回的流中读取数据 /// </summary> /// <param name="request"></param> /// <param name="serverStreamWriter"></param> /// <param name="context"></param> /// <returns></returns> public override async Task GetStudent(StudentRequest request, IServerStreamWriter<StudentReply> serverStreamWriter, ServerCallContext context) { List<StudentReply> studentsReply = students; if (!string.IsNullOrEmpty(request.Name)) { studentsReply = students.Where(x => x.Name == request.Name).ToList(); } //逐个写入 foreach (var item in studentsReply) { await serverStreamWriter.WriteAsync(item); System.Threading.Thread.Sleep(1000); } } /// <summary> /// 客户端流RPC模式,IAsyncStreamReader 读取客户端数据 /// </summary> /// <param name="asyncStreamReader"></param> /// <param name="context"></param> /// <returns></returns> public override async Task<StudentReply> AddStudent(IAsyncStreamReader<StudentRequest> asyncStreamReader, ServerCallContext context) { List<StudentRequest> studentRequests = new List<StudentRequest>(); //获取客户端多次传来的数据 while (await asyncStreamReader.MoveNext()) { studentRequests.Add(asyncStreamReader.Current); } return new StudentReply() { Message = "新增" + studentRequests.Count + "个学生" }; } /// <summary> /// 双流RPC,参数需要按顺序来写:IAsyncStreamReader、IServerStreamWriter、ServerCallContext /// </summary> /// <param name="asyncStreamReader"></param> /// <param name="context"></param> /// <returns></returns> public override async Task Examination(IAsyncStreamReader<StudentRequest> asyncStreamReader, IServerStreamWriter<StudentReply> serverStreamWriter, ServerCallContext context) { //获取客户端多次传来的数据 while (await asyncStreamReader.MoveNext()) { //写入服务器流 await serverStreamWriter.WriteAsync(new StudentReply { Name = asyncStreamReader.Current.Name, Age = asyncStreamReader.Current.Age, Gender = asyncStreamReader.Current.Gender, Message = "考试分数:" + new Random().Next(0, 100) }); } } } }
7、新建一个客户端项目,控制台程序GrpcClient1,把服务端的Protos文件夹复制到客户端,注意把proto文件的命名空间改成客户端的命名空间 option csharp_namespace = "GrpcClient1";
8、修改客户端 .csproj 文件,GrpcServices要修改为 Client
<ItemGroup> <Protobuf Include="Protos\greet.proto" GrpcServices="Client" /> <Protobuf Include="Protos\student.proto" GrpcServices="Client" /> </ItemGroup>
9、重新生成客户端项目,在目录GrpcService1\GrpcClient1\obj\Debug\netcoreapp3.1\Protos下应该会生成对应的Greet.cs、GreetGrpc.cs、Student.cs、StudentGrpc.cs
客户端调用RPC服务,先把服务跑起来(在服务项目的文件根目录 cmd》dotnet run),然后再跑客户端。
using Grpc.Net.Client; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace GrpcClient1 { class Program { private static List<StudentRequest> students = new List<StudentRequest>() { new StudentRequest(){ Name = "jay",Age=19, Gender = Gender.Male }, new StudentRequest(){ Name = "robot",Age=13, Gender = Gender.Male }, new StudentRequest(){ Name = "Melissa",Age=22, Gender = Gender.Female }, new StudentRequest(){ Name = "Alice",Age=18, Gender = Gender.Female }, }; static async Task Main(string[] args) { //await SayHello();// 标准RPC //await GetStudent();// 服务端流RPC //await AddStudent();// 客户端流RPC await Examination();// 双流RPC Console.ReadKey(); } /// <summary> /// 标准RPC /// </summary> /// <returns></returns> private static async Task SayHello() { var channel = GrpcChannel.ForAddress("https://localhost:5001"); var client = new Student.StudentClient(channel); var ret = await client.SayHelloAsync(new StudentRequest { Name = "jay" }); Console.WriteLine("result: " + ret.Message); Console.WriteLine("Press any key to exit..."); } /// <summary> /// 服务端流RPC /// </summary> /// <returns></returns> private static async Task GetStudent() { var channel = GrpcChannel.ForAddress("https://localhost:5001"); var client = new Student.StudentClient(channel); var res = client.GetStudent(new StudentRequest { Name = "" }); var rs = res.ResponseStream; //通过这里判断任务状态 CancellationTokenSource cancellationToken = new CancellationTokenSource(); //通过while等待服务器返回 任务令牌 Token while (await rs.MoveNext(cancellationToken.Token)) { Console.WriteLine(rs.Current.ToString()); Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); } } /// <summary> /// 客户端流RPC /// </summary> /// <returns></returns> private static async Task AddStudent() { var channel = GrpcChannel.ForAddress("https://localhost:5001"); var client = new Student.StudentClient(channel); var clientStreamingCall = client.AddStudent(); foreach (var item in students) { //把数据写入请求流中 await clientStreamingCall.RequestStream.WriteAsync(item); Thread.Sleep(1000); } //告诉服务器,数据传输完成 await clientStreamingCall.RequestStream.CompleteAsync(); string message = clientStreamingCall.ResponseAsync.Result.Message; Console.WriteLine("结果:" + message); } /// <summary> /// 双流RPC /// </summary> /// <returns></returns> private static async Task Examination() { //服用通道 var channel = GrpcChannel.ForAddress("https://localhost:5001"); var client = new Student.StudentClient(channel); var clientStreamingCall = client.Examination(); //从服务器流读取返回数据,开启新线程处理服务器端返回的数据,不要等客户端发送所有数据 var rs = clientStreamingCall.ResponseStream; CancellationTokenSource cancellationToken = new CancellationTokenSource(); var tk = Task.Run(async () => { while (await rs.MoveNext(cancellationToken.Token)) { Console.WriteLine(rs.Current.ToString()); } }); //客户端流写入数据 foreach (var item in students) { await clientStreamingCall.RequestStream.WriteAsync(item); } //告诉服务器,数据传输完成 await clientStreamingCall.RequestStream.CompleteAsync(); //等待完成服务器数据接受 await tk; } } }
服务端项目目录下》cmd》运行dotnet run,可能会net core启动报错 unable to start kestrel Unable to configure HTTPS endpoint. No server certificate was specified,
这是因为net core默认使用的https,如果使用Kestrel web服务器的话没有安装证书就会报这个错,在报错信息中有说明,让你执行 dotnet dev-certs https --trust 安装证书,安装成功就好了。
10、gRPC服务支持的流组合:
一元(没有流媒体): 简单rpc 这就是一般的rpc调用,一个请求对象对应一个返回对象。客户端发起一次请求,服务端响应一个数据,即标准RPC通信。
服务器到客户端流:客户端流式rpc 客户端传入多个请求对象,服务端返回一个响应结果。应用场景:物联网终端向服务器报送数据。
客户端到服务器流:服务端流式rpc 一个请求对象,服务端可以传回多个结果对象。服务端流 RPC 下,客户端发出一个请求,但不会立即得到一个响应,而是在服务端与客户端之间建立一个单向的流,服务端可以随时向流中写入多个响应消息,最后主动关闭流,而客户端需要监听这个流,不断获取响应直到流关闭。应用场景举例:典型的例子是客户端向服务端发送一个股票代码,服务端就把该股票的实时数据源源不断的返回给客户端。
双向流媒体:双向流式rpc 结合客户端流式rpc和服务端流式rpc,可以传入多个对象,返回多个响应对象。应用场景:聊天应用。
示例源码:链接:https://pan.baidu.com/s/1FkJ7VqsmYEDBuN9aIRiInA 提取码:8kyg
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
2016-06-15 C#缓存-依赖 CacheHelper