ASP.NET Core gRPC(Google Remote Procedure Call)介绍:https://zhuanlan.zhihu.com/p/411315625
参考: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

posted on 2022-06-15 16:25  邢帅杰  阅读(131)  评论(0编辑  收藏  举报