zoukankan      html  css  js  c++  java
  • Asp.Net Core Grpc 入门实践

    Grpc简介

    gRPC 是一种与语言无关的高性能远程过程调用 (RPC) 框架。
    在 gRPC 中,客户端应用程序可以直接调用不同计算机上的服务器应用程序上的方法,就像它是本地对象一样,从而更轻松地创建分布式应用程序和服务。它基于定义服务的想法,指定了参数和返回类型的远程过程调用的方法。服务器端实现这个接口并运行grpc服务来处理客户端的请求,客户端调用相同的方法完成请求。

    grpc官网
    .NET 上的 gRPC 的简介

    gRPC 的主要优点是:

    现代高性能轻量级 RPC 框架。
    协定优先 API 开发,默认使用协议缓冲区(Protocol Buffers),允许与语言无关的实现。
    可用于多种语言的工具,以生成强类型服务器和客户端。
    支持客户端、服务器和双向流式处理调用。
    使用 Protobuf 二进制序列化减少对网络的使用。

    这些优点使 gRPC 适用于:

    效率至关重要的轻量级微服务。
    需要多种语言用于开发的 Polyglot 系统。
    需要处理流式处理请求或响应的点对点实时服务。
    低延迟、高度可扩展的分布式系统。
    开发与云服务器通信的移动客户端。
    设计一个需要准确、高效且独立于语言的新协议。
    分层设计,以启用扩展,例如。身份验证、负载平衡、日志记录和监控等

    Protocol Buffers

    protocol-buffers详细介绍
    在C#中会生成一个名为FirstMessage的类,基本格式如下:
    first.proto

    syntax="proto3"; //指定协议版本
    
    package my.project;//C# namespace MyProject
    
    option csharp_namespace="GrpcDemo.Protos"; //生成C#代码时命名空间
    
    message FirstMessage{
      int32 id=1;
      string name=2;
      bool is_male=3;
    }
    

    定义服务:

    指定输入HelloRequest和输出HelloReply,以及方法名SayHello
    C#会生成对应的类和方法。

    // The greeter service definition.
    
    service Greeter {
      // Sends a greeting
      rpc SayHello (HelloRequest) returns (HelloReply) {}
    }
    
    // The request message containing the user's name.
    message HelloRequest {
      string name = 1;
    }
    
    // The response message containing the greetings
    message HelloReply {
      string message = 1;
    

    指定字段数据类型

    字段编号

    每个字段都会有一个唯一的字段编号,这个非常重要。json中传递数据是以字段名为key,protobuf 是以字段编号为主,所以不要轻易变化编号。
    范围 1 到 15 中的字段编号需要一个字节进行编码,范围 16 到 2047 中的字段编号需要两个字节。所以需要为使用频繁的字段编号预留字段号1到15,并且为可能添加的元素预留一点字段号。

    指定字段规则

    • required:必填效验?
    • optional ?
    • repeated 可以出现多次,类似list

    四种定义方式

    Rpc生命周期对比

    一元Unary RPC

    rpc SayHello(HelloRequest) returns (HelloResponse);

    服务流Server streaming Rpcs

    rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);

    客户端流Client streaming RPCs

    rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);

    双向流Bidirectional streaming RPCs

    rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);

    Metadata元数据

    可以传递特定的Rpc信息(如身份信息)。形式为键值对。

    var md = new Metadata {
                    { "username","zhangsan"},
                    { "role","administrator"}
                };
    //或者
    var headers = new Metadata();
        headers.Add("Authorization", $"Bearer {token}");
    

    Channels 渠道

    gRPC 通道提供与指定主机和端口上的 gRPC 服务器的连接。

    using var channel = GrpcChannel.ForAddress("https://localhost:5001");
    var client = new EmployeeService.EmployeeServiceClient(channel);
    

    环境搭建

    下载protobuf

    解压后,配置环境变量Path: D:Protoprotoc-3.14.0-win64in
    然后cmd确认安装成功:

    C:UsersAdministrator>protoc --version
    libprotoc 3.14.0
    

    代码实践

    环境说明:

    visual studio 2019 16.8.5
    C:UsersAdministrator>dotnet --version
    5.0.103
    

    服务端

    演示特别说明:
    1、client流采用分批上传图片演示。
    2、服务端流采用将list数据分批传回客户端。

    新建Gprc服务项目(或普通asp.netcore web项目)

    1、需要依赖以下nuget包

    Grpc.AspNetCore
    Grpc.Tools
    Grpc.Net.Client 控制台需要
    Google.Protobuf

    2、然后新建 Protos文件夹,定义proto文件

    syntax="proto3";
    option csharp_namespace="GrpcDemo.Protos";
    
    message Employee{
     int32 id=1;
     int32 no=2;
     string firstName=3;
     string lastName=4;
     float salary=5;//薪水
    }
    
    message GetByNoRequest{
     int32 no=1;
    }
    
    message EmployeeResonse{
    Employee employee=1;
    }
    
    message GetAllReqeust{}
    
    message AddPhotoRequest{
    bytes data=1;
    }
    message AddPhotoResponse{
     bool isOk=1;
    }
    
    message EmployeeRequest{
    Employee employee=1;
    }
    
    service EmployeeService{
    //Unary Rpc示例
     rpc GetByNo(GetByNoRequest) returns(EmployeeResonse);
     //server streaming Rpc示例
     rpc GetAll(GetAllReqeust) returns(stream EmployeeResonse);
     //client streaming Rpc示例
     rpc AddPhoto(stream AddPhotoRequest) returns(AddPhotoResponse);
     //双向Rpc示例
     rpc SaveAll(stream EmployeeRequest) returns(stream EmployeeResonse);
    }
    

    设置proto属性,

    然后编译,会生成一个服务定义类以及相关的方法。

    注意:EmployeeService.EmployeeServiceBase是有Grpc组件根据proto文件生成的。

        public class MyEmployeeService : EmployeeService.EmployeeServiceBase
        {
            private readonly ILogger<MyEmployeeService> _logger;
            public MyEmployeeService(ILogger<MyEmployeeService> logger)
            {
                _logger = logger;
            }
            /// <summary>
            /// Unary Rpc
            /// </summary>
            /// <param name="request"></param>
            /// <param name="context"></param>
            /// <returns></returns>
            public override async Task<EmployeeResonse> GetByNo(GetByNoRequest request, ServerCallContext context)
            {
                Console.WriteLine("
    GrpcServer即将为你演示 一元Unary Rpc");
    
                MetadataProcess(context);
    
                var data = InmemoryData.Employees.FirstOrDefault(m => m.No == request.No);
                if (data != null)
                {
                    return await Task.FromResult(new EmployeeResonse()
                    {
                        Employee = data
                    });
                }
                throw new Exception("异常");
            }
    
            private void MetadataProcess(ServerCallContext context)
            {
                var metaData = context.RequestHeaders;
                foreach (var item in metaData)
                {
                    _logger.LogInformation($"key:{item.Key},value:{item.Value}");
                }
            }
    
            /// <summary>
            /// 服务流Server streaming Rpcs
            /// </summary>
            /// <param name="request"></param>
            /// <param name="responseStream"></param>
            /// <param name="context"></param>
            /// <returns></returns>
            public override async Task GetAll(GetAllReqeust request, IServerStreamWriter<EmployeeResonse> responseStream, ServerCallContext context)
            {
                Console.WriteLine("
    GrpcServer即将为你演示 服务流Server streaming Rpcs");
    
                MetadataProcess(context);
                foreach (var employee in InmemoryData.Employees)
                {
                    Console.WriteLine($"responseStream.Write:{employee}");
                    await responseStream.WriteAsync(new EmployeeResonse()
                    {
                        Employee = employee
                    });
                }
            }
            /// <summary>
            /// 客户端流Client streaming RPCs 
            /// </summary>
            /// <param name="requestStream"></param>
            /// <param name="context"></param>
            /// <returns></returns>
            public override async Task<AddPhotoResponse> AddPhoto(IAsyncStreamReader<AddPhotoRequest> requestStream, ServerCallContext context)
            {
                Console.WriteLine("
    GrpcServer即将为你演示 客户端流Client streaming RPCs ");
                MetadataProcess(context);
    
                var data = new List<byte>();
                while (await requestStream.MoveNext())
                {
                    Console.WriteLine($"Received:{requestStream.Current.Data.Length}");
                    data.AddRange(requestStream.Current.Data);
                }
    
                Console.WriteLine($"Received file with{data.Count} bytes");
    
                return new AddPhotoResponse { IsOk = true };
            }
    
            /// <summary>
            /// 双向流Bidirectional streaming RPCs
            /// </summary>
            /// <param name="requestStream"></param>
            /// <param name="responseStream"></param>
            /// <param name="context"></param>
            /// <returns></returns>
            public override async Task SaveAll(IAsyncStreamReader<EmployeeRequest> requestStream, IServerStreamWriter<EmployeeResonse> responseStream, ServerCallContext context)
            {
                Console.WriteLine("
    GrpcServer即将为你演示 双向流Bidirectional streaming RPCs");
    
                while (await requestStream.MoveNext()) {
    
                    var employee = requestStream.Current.Employee;
                    Console.WriteLine($"requestStream.Current:{employee}");
                    lock (this)
                    {
                        InmemoryData.Employees.Add(employee);
                    }
                    Console.WriteLine($"responseStream.Write:{employee}");
                    await responseStream.WriteAsync(new EmployeeResonse()
                    {
                        Employee = employee
                    });
                }
            }
        }
    }
    

    客户端

    同上面新建Console项目,并引用以下nuget包:

    Google.Protobuf
    Grpc.Net.Client
    Google.Protobuf
    Grpc.Tools
    新建protos文件夹,复制proto文件(或引用其他管理方案,如在线地址),然后编译生成解决方案:

    创建通道

       static async Task Main(string[] args)
            {
                using var channel = GrpcChannel.ForAddress("https://localhost:5001");
                var client = new EmployeeService.EmployeeServiceClient(channel);
                var md = new Metadata {
                    { "username","zhangsan"},
                    { "role","administrator"},
                    { "Authorization", $"Bearer xxxxxxxxxxxxxxxxxx" }
                };
    
                Console.WriteLine("
    GrpcClient即将为你演示 一元Unary Rpc");
                            await GetByNoAsync(client, md);
    
                Console.WriteLine("
    GrpcClient即将为你演示 服务流Server streaming Rpcs");
                await GetAll(client, md);
    
                Console.WriteLine("
    GrpcClient即将为你演示 客户端流Client streaming RPCs ");
                await AddPhoto(client,md);
    
                Console.WriteLine("
    GrpcClient即将为你演示 双向流Bidirectional streaming RPCs");
                await SaveAll(client, md);
                Console.WriteLine("Press Any key Exit!");
                Console.Read();
            }
    

    然后对接服务端四种服务流方式:

    
            /// <summary>
            /// Unary RPC一元RPC
            /// </summary>
            static async Task GetByNoAsync(EmployeeServiceClient client, Metadata md)
            {
    
                //一元
                var response = await client.GetByNoAsync(new GetByNoRequest()
                {
                    No = 1
                }, md);
    
                Console.WriteLine($"Reponse:{response}");
            }
    
            /// <summary>
            /// server-stream
            /// </summary>
            /// <param name="client"></param>
            /// <param name="md"></param>
            /// <returns></returns>
            static async Task GetAll(EmployeeServiceClient client, Metadata md)
            {
                using var call = client.GetAll(new GetAllReqeust() { });
                var responseStream = call.ResponseStream;
                while (await responseStream.MoveNext())
                {
                    Console.WriteLine(responseStream.Current.Employee);
                }
    
            }
    
            /// <summary>
            /// client-stream
            /// </summary>
            /// <param name="client"></param>
            /// <param name="md"></param>
            /// <returns></returns>
            static async Task AddPhoto(EmployeeServiceClient client, Metadata md)
            {
                FileStream fs = File.OpenRead("Q1.png");
                using var call = client.AddPhoto(md);
                var stram = call.RequestStream;
    
                while (true)
                {
                    byte[] buffer = new byte[1024];
                    int numRead = await fs.ReadAsync(buffer, 0, buffer.Length);
                    if (numRead == 0)
                    {
                        break;
                    }
                    if (numRead < buffer.Length)
                    {
                        Array.Resize(ref buffer, numRead);
                    }
                    await stram.WriteAsync(new AddPhotoRequest()
                    {
                        Data = ByteString.CopyFrom(buffer)
                    });
                }
    
                await stram.CompleteAsync();
                var res = await call.ResponseAsync;
    
    
                Console.WriteLine(res.IsOk);
            }
    
            /// <summary>
            /// 双向流
            /// </summary>
            /// <param name="client"></param>
            /// <param name="md"></param>
            /// <returns></returns>
            static async Task SaveAll(EmployeeServiceClient client, Metadata md)
            {
                var employees = new List<Employee>() {
                 new Employee(){ Id=10, FirstName="F10", LastName="L10", No=10, Salary=10 },
                 new Employee(){ Id=11, FirstName="F11", LastName="L11", No=11, Salary=11 },
                 new Employee(){ Id=12, FirstName="F12", LastName="L12", No=12, Salary=12 },
                };
    
                using var call = client.SaveAll(md);
                var requestStream = call.RequestStream;
                var responseStream = call.ResponseStream;
    
                var responseTask = Task.Run(async () =>
                {
                    while (await responseStream.MoveNext())
                    {
                        Console.WriteLine($"response:{responseStream.Current.Employee}");
                    }
                });
    
                foreach (var employee in employees) {
                    await requestStream.WriteAsync(new EmployeeRequest()
                    {
                        Employee = employee
                    });
                }
                await requestStream.CompleteAsync();
                await responseTask;
            }
    

    效果演示如下:

    客户端:

    GrpcClient即将为你演示 一元Unary Rpc
    Reponse:{ "employee": { "id": 1, "no": 1, "firstName": "FN1", "lastName": "LN1", "salary": 1 } }
    
    GrpcClient即将为你演示 服务流Server streaming Rpcs
    { "id": 1, "no": 1, "firstName": "FN1", "lastName": "LN1", "salary": 1 }
    { "id": 2, "no": 2, "firstName": "FN1", "lastName": "LN1", "salary": 1 }
    { "id": 3, "no": 3, "firstName": "FN1", "lastName": "LN1", "salary": 1 }
    { "id": 4, "no": 4, "firstName": "FN1", "lastName": "LN1", "salary": 1 }
    
    GrpcClient即将为你演示 客户端流Client streaming RPCs
    True
    
    GrpcClient即将为你演示 双向流Bidirectional streaming RPCs
    response:{ "id": 10, "no": 10, "firstName": "F10", "lastName": "L10", "salary": 10 }
    response:{ "id": 11, "no": 11, "firstName": "F11", "lastName": "L11", "salary": 11 }
    response:{ "id": 12, "no": 12, "firstName": "F12", "lastName": "L12", "salary": 12 }
    Press Any key Exit!
    

    服务端输出:

    GrpcServer即将为你演示 一元Unary Rpc
    info: GrpcDemo.Services.MyEmployeeService[0]
          key:authorization,value:Bearer xxxxxxxxxxxxxxxxxx
    info: GrpcDemo.Services.MyEmployeeService[0]
          key:user-agent,value:grpc-dotnet/2.35.0.0
    info: GrpcDemo.Services.MyEmployeeService[0]
          key:username,value:zhangsan
    info: GrpcDemo.Services.MyEmployeeService[0]
          key:role,value:administrator
    
    GrpcServer即将为你演示 服务流Server streaming Rpcs
    info: GrpcDemo.Services.MyEmployeeService[0]
          key:user-agent,value:grpc-dotnet/2.35.0.0
    responseStream.Write:{ "id": 1, "no": 1, "firstName": "FN1", "lastName": "LN1", "salary": 1 }
    responseStream.Write:{ "id": 2, "no": 2, "firstName": "FN1", "lastName": "LN1", "salary": 1 }
    responseStream.Write:{ "id": 3, "no": 3, "firstName": "FN1", "lastName": "LN1", "salary": 1 }
    responseStream.Write:{ "id": 4, "no": 4, "firstName": "FN1", "lastName": "LN1", "salary": 1 }
    
    GrpcServer即将为你演示 客户端流Client streaming RPCs
    info: GrpcDemo.Services.MyEmployeeService[0]
          key:authorization,value:Bearer xxxxxxxxxxxxxxxxxx
    info: GrpcDemo.Services.MyEmployeeService[0]
          key:user-agent,value:grpc-dotnet/2.35.0.0
    info: GrpcDemo.Services.MyEmployeeService[0]
          key:username,value:zhangsan
    info: GrpcDemo.Services.MyEmployeeService[0]
          key:role,value:administrator
    Received:1024
    Received:1024
    Received:1024
    Received:1024
    Received:1024
    Received:1024
    Received:1024
    Received:1024
    Received:1024
    Received:573
    Received file with9789 bytes
    
    GrpcServer即将为你演示 双向流Bidirectional streaming RPCs
    requestStream.Current:{ "id": 10, "no": 10, "firstName": "F10", "lastName": "L10", "salary": 10 }
    responseStream.Write:{ "id": 10, "no": 10, "firstName": "F10", "lastName": "L10", "salary": 10 }
    requestStream.Current:{ "id": 11, "no": 11, "firstName": "F11", "lastName": "L11", "salary": 11 }
    responseStream.Write:{ "id": 11, "no": 11, "firstName": "F11", "lastName": "L11", "salary": 11 }
    requestStream.Current:{ "id": 12, "no": 12, "firstName": "F12", "lastName": "L12", "salary": 12 }
    responseStream.Write:{ "id": 12, "no": 12, "firstName": "F12", "lastName": "L12", "salary": 12 }
    

    扩展了解

    日志配置

    [从 gRPC 创建 JSON Web API](https://docs.microsoft.com/zh-cn/aspnet/core/grpc/httpapi?view=aspnetcore-5.0)

    源码地址

    GrpcDemo和GrpcDemo.Client

    参考资料

    Grpc官网
    gRPC in C#*2/Go/C++

    感谢观看,本篇实践到此结束。

  • 相关阅读:
    《.NET内存管理宝典 》(Pro .NET Memory Management) 阅读指南
    《.NET内存管理宝典 》(Pro .NET Memory Management) 阅读指南
    《.NET内存管理宝典 》(Pro .NET Memory Management) 阅读指南
    使用Jasmine和karma对传统js进行单元测试
    《.NET内存管理宝典 》(Pro .NET Memory Management) 阅读指南
    《.NET内存管理宝典 》(Pro .NET Memory Management) 阅读指南
    nginx 基于IP的多虚拟主机配置
    Shiro 框架的MD5加密算法实现原理
    项目实战:Qt+OSG三维点云引擎(支持原点,缩放,单独轴或者组合多轴拽拖旋转,支持导入点云文件)
    实用技巧:阿里云服务器建立公网物联网服务器(解决阿里云服务器端口,公网连接不上的问题)
  • 原文地址:https://www.cnblogs.com/fancunwei/p/14405938.html
Copyright © 2011-2022 走看看