.Net Core gRpc调用

  • .Net Core gRpc调用已关闭评论
  • 191 次浏览
  • A+
所属分类:.NET技术
摘要

高性能、开源的通用 RPC 框架实现不同语言相互调用官网Protobuf 消息参考proto3 语言指南

简介

创建gRPC

创建服务端

  1. vs2022直接搜索grpc默认下一步创建

创建控制台测试

  1. 创建控制台
  2. 引入以下dll
<PackageReference Include="Google.Protobuf" Version="3.23.4" /> <PackageReference Include="Grpc.Net.Client" Version="2.55.0" /> <PackageReference Include="Grpc.Tools" Version="2.56.2"> 
  1. 打开服务端.csproj文件,复制以下内容粘贴到客户端的.csproj文件中并修改GrpcServices=Client,客户端.csproj文件出现 None Update="Protosgreet.proto" 这组ItemGroup是可以删除的
<ItemGroup>   <Protobuf Include="Protosgreet.proto" GrpcServices="Client" /> </ItemGroup> 

4.将服务端Protos文件夹及内容全部拷贝到客户端项目下
5.在客户端创建gRpcRequest.cs文件并增加下列代码,端口号填写服务端端口

using Grpc.Net.Client; using GrpcService; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using static GrpcService.Greeter;  namespace gRpcConsoleTest {     public static class gRpcRequest     {         public static async Task SayHello()         {             using(var channel = GrpcChannel.ForAddress("https://localhost:7166"))              {                 GreeterClient client = new GreeterClient(channel);                 HelloReply reply = await client.SayHelloAsync(new HelloRequest() {Name = "jjjjj" });                 Console.WriteLine(reply.Message);             }         }     } } 

6.客户端Program.cs文件中加入测试代码 await gRpcRequest.SayHello();
7.服务端和客户端在想要调试的位置打上断点
8.运行服务端
9.选中客户端项目右键 -> 调试 -> 启动新实例 即可两个项目全部命中断点进行调试测试

创建自定义服务

  • 服务端

    1. Protos文件夹添加 custom.proto文件并添加下列代码,重新生成项目打开项目所在文件夹,打开路径:objDebugnet6.0Protos 查看CustomGrpc.cs是否存在,如果没有存在则在.csproj文件中添加:
    syntax = "proto3";  option csharp_namespace = "Custom.Service";  package custom;  service CustomGreeter {   rpc Plus(Number) returns (NumberResult) ; }  message Number {     int32 leftNumber = 1;     int32 rightNumber = 2; }    message NumberResult{     int32 result = 1; } 
    1. Services文件夹下添加CustomGreeterService.cs文件,namespace 与 .protos中的csharp_namespace对应
    using Grpc.Core;  namespace Custom.Service {     public class CustomGreeterService : CustomGreeter.CustomGreeterBase     {         public override async Task<NumberResult> Plus(Number request, ServerCallContext context) =>              await Task.FromResult<NumberResult>(new NumberResult()             {                 Result = request.LeftNumber + request.RightNumber,             });     } } 

    3.在Program.cs 中注册新创建的服务,加入下列代码:
    app.MapGrpcService();

  • 客户端

    1. 将服务端的custom.proto文件拷贝到Protos文件夹内并在.csproj文件中添加(注意这里GrpcServices="Client"):
    2. 重新生成项目并检查路径: objDebugnet6.0Protos 是否生成对应的CustomGrpc.cs文件
    3. 加入测试代码:
    public static async Task Plus(int leftNumber,int rightNumber) {     using (var channel = GrpcChannel.ForAddress("https://localhost:7166"))     {         CustomGreeterClient client = new CustomGreeterClient(channel);         NumberResult number = await client.PlusAsync(new Custom.Service.Number() { LeftNumber = leftNumber,RightNumber = rightNumber});         Console.WriteLine(number.Result);     } } 
    1. 测试步骤与上面控制台测试一样,调试方式也一样

服务器流式处理方法

custom.proto

syntax = "proto3";  option csharp_namespace = "Custom.Service";  package custom;  service CustomGreeter {   rpc SelfIncreaseServer(IntArrayModel) returns (stream BathTheCatResp); //服务端流 }  message BathTheCatResp{     string message = 1; }  message IntArrayModel{     repeated int32 number = 1; } 

CustomGreeterService.cs

using Grpc.Core;  namespace Custom.Service {     public class CustomGreeterService : CustomGreeter.CustomGreeterBase     {         public override async Task SelfIncreaseServer(IntArrayModel request, IServerStreamWriter<BathTheCatResp> responseStream, ServerCallContext context)         {             foreach (var item in request.Number)             {                 Console.WriteLine($"客户端传入参数: {item}");                 await responseStream.WriteAsync(new BathTheCatResp() { Message = item.ToString()});                 await Task.Delay(1000);             }         }     } } 

gRpcRequest.cs

using Custom.Service; using Grpc.Core; using Grpc.Net.Client; using GrpcService; using static Custom.Service.CustomGreeter;  namespace gRpcConsoleTest {     public static class gRpcRequest     {         public static async Task SelfIncreaseServe()         {             using (var channel = GrpcChannel.ForAddress("https://localhost:7166"))             {                 CustomGreeterClient client = new CustomGreeterClient(channel);                 IntArrayModel intArray = new IntArrayModel();                 for (int i = 0; i < 10; i++)                 {                     intArray.Number.Add(i);                    }                  var batch = client.SelfIncreaseServer(intArray);                  Task batchTask = Task.Run(async ()=>                 {                     await foreach (var item in batch.ResponseStream.ReadAllAsync())                     {                         Console.WriteLine($"服务端相应数据: {item.Message}");                     }                 });                  await batchTask;             }         }     } } 

客户端流式处理方法

custom.proto

syntax = "proto3";  option csharp_namespace = "Custom.Service";  package custom;  service CustomGreeter {   rpc SelfIncreaseClient(stream BathTheCatReq) returns (IntArrayModel); //客户端流 }  message BathTheCatResp{     string message = 1; }  message IntArrayModel{     repeated int32 number = 1; } 

CustomGreeterService.cs

using Grpc.Core;  namespace Custom.Service {     public class CustomGreeterService : CustomGreeter.CustomGreeterBase     {         public override async Task<IntArrayModel> SelfIncreaseClient(IAsyncStreamReader<BathTheCatReq> requestStream, ServerCallContext context)         {             IntArrayModel result = new IntArrayModel();             while (await requestStream.MoveNext())             {                 var message = requestStream.Current;                   Console.WriteLine($"客户端流传入消息: {message}");                 result.Number.Add(message.Id + 1);             }             return result;         }     } } 

gRpcRequest.cs

using Custom.Service; using Grpc.Core; using Grpc.Net.Client; using GrpcService; using static Custom.Service.CustomGreeter;  namespace gRpcConsoleTest {     public static class gRpcRequest     {         public static async Task SelfIncreaseClient()         {             using (var channel = GrpcChannel.ForAddress("https://localhost:7166"))             {                 CustomGreeterClient client = new CustomGreeterClient(channel);                 var batch = client.SelfIncreaseClient();                 for (int i = 0; i < 10; i++)                 {                     await batch.RequestStream.WriteAsync(new BathTheCatReq() { Id = i });                     await Task.Delay(1000);                 }                 await batch.RequestStream.CompleteAsync();                  foreach (var item in batch.ResponseAsync.Result.Number)                 {                     Console.WriteLine($"响应数据: {item}");                 }             }         }     } } 

双向流式处理方法

custom.proto

syntax = "proto3";  option csharp_namespace = "Custom.Service";  package custom;  service CustomGreeter {   rpc SelfIncreaseDouble(stream BathTheCatReq) returns (stream BathTheCatResp);//双端流 }  message BathTheCatReq{     int32 id = 1; }  message BathTheCatResp{     string message = 1; } 

CustomGreeterService.cs

using Grpc.Core;  namespace Custom.Service {     public class CustomGreeterService : CustomGreeter.CustomGreeterBase     {         public override async Task SelfIncreaseDouble(IAsyncStreamReader<BathTheCatReq> requestStream, IServerStreamWriter<BathTheCatResp> responseStream, ServerCallContext context)         {             while (await requestStream.MoveNext())             {                 var message = requestStream.Current.Id;                 Console.WriteLine($"客户端流传入消息: {message}");                 await responseStream.WriteAsync(new BathTheCatResp() { Message=(message+1).ToString()});             }         }     } } 

gRpcRequest.cs

using Custom.Service; using Grpc.Core; using Grpc.Net.Client; using GrpcService; using static Custom.Service.CustomGreeter; using static GrpcService.Greeter;  namespace gRpcConsoleTest {     public static class gRpcRequest     {         public static async Task SelfIncreaseDouble()         {             using (var channel = GrpcChannel.ForAddress("https://localhost:7166"))             {                 CustomGreeterClient client = new CustomGreeterClient(channel);                 var batch = client.SelfIncreaseDouble();                  Task batchTask = Task.Run(async () =>                 {                     await foreach (var item in batch.ResponseStream.ReadAllAsync())                     {                         Console.WriteLine($"服务端相应数据: {item.Message}");                     }                 });                   for (int i = 0; i < 10; i++)                 {                     await batch.RequestStream.WriteAsync(new BathTheCatReq() { Id = i });                     await Task.Delay(1000);                 }                  await batchTask;             }         }     } } 

.Net Core 调用gRpc

项目引用

<PackageReference Include="Google.Protobuf" Version="3.24.0" /> <PackageReference Include="Grpc.Net.Client" Version="2.55.0" /> <PackageReference Include="Grpc.Net.ClientFactory" Version="2.55.0" /> <PackageReference Include="Grpc.Tools" Version="2.56.2">  <ItemGroup>   <Protobuf Include="Protoscustom.proto" GrpcServices="Client" />   <Protobuf Include="Protosgreet.proto" GrpcServices="Client" /> </ItemGroup> 

Program.cs

//CustomGreeterClient grpc连接类 builder.Services.AddGrpcClient<CustomGreeterClient>(options => {     options.Address = new Uri("https://localhost:7166"); //grpc 服务地址 }); 

gRpcController.cs

using Custom.Service; using Microsoft.AspNetCore.Mvc; using static Custom.Service.CustomGreeter;  namespace gRpcWebAPI.Controllers {     [Route("api/[controller]/[action]")]     [ApiController]     public class gRpcController : ControllerBase     {         CustomGreeterClient _client; //使用构造函数注入         public gRpcController(CustomGreeterClient client)         {             _client = client;         }          [HttpGet]         public async Task<IActionResult> Plus(int leftNumber, int rightNumber)         {             NumberResult number = await _client.PlusAsync(new Custom.Service.Number() { LeftNumber = leftNumber, RightNumber = rightNumber });             return new JsonResult(number);         }     } } 

支持Aop

  1. 服务端, 客户端 都需要继承 Interceptor
  2. 重新需要实现Aop的方法,如服务端: UnaryServerHandler,客户端: AsyncUnaryCall 针对一元调用的Aop
  3. 我这里使用的是NLog写的日志,NLog可以使用可以翻阅我先前的博客
    .Net Core NLog+oracel

服务端 Program.cs

builder.Services.AddGrpc(options => {     options.Interceptors.Add<LogInterceptor>(); }); 

服务端 LogInterceptor.cs

using Grpc.Core; using Grpc.Core.Interceptors;  namespace GrpcService.Interceptors {     public class LogInterceptor : Interceptor     {         ILogger<LogInterceptor> _logger;         public LogInterceptor(ILogger<LogInterceptor> logger)         {             _logger = logger;         }         public override Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation)         {             _logger.LogInformation("===========UnaryServerHandler==========");             return continuation(request, context);         }     } } 

客户端 Program.cs

builder.Services.AddGrpcClient<CustomGreeterClient>(options => {     options.Address = new Uri("https://localhost:7166"); //服务端地址 }).AddInterceptor<LogInterceptor>(); 

客户端 LogInterceptor.cs

using Grpc.Core; using Grpc.Core.Interceptors; namespace GrpcService.Interceptors {     public class LogInterceptor : Interceptor     {         ILogger<LogInterceptor> _logger;         public LogInterceptor(ILogger<LogInterceptor> logger)         {             _logger = logger;         }         public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation)         {             _logger.LogInformation("===========AsyncUnaryCall===========");             return continuation(request, context);         }     } } 

jwt+gRPC验证

  1. 准备单独的一个网站发布jwt Token (授权中心)
  2. 然后在gRPC项目中jwt鉴权 和 获取到角色信息之后授权
  3. webapi测试gRPC调用, 先在授权中心获取token然后在 ConfigureChannel 方法中设置gRPC全局的jwt token
  4. 返回401: jwt鉴权不通过
  5. 返回403: jwt授权不通过

准备Jwt Token发布中心

Program.cs

//读取Jwt配置 builder.Services.Configure<JwtConfig>(builder.Configuration.GetSection("JwtTokenOptions")); 

JwtConfig.cs

namespace AuthenorizationCenter.Tools.Model {     public class JwtConfig     {         public string? Audience { get; set; }         public string? Issuer { get; set; }         public string? SecurityKey { get; set; }         public int ExpiresMinutes { get; set; }     } } 

AuthenorizationController.cs

using AuthenorizationCenter.Tools; using AuthenorizationCenter.Tools.Model; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options;  namespace AuthenorizationCenter.Controllers {     [Route("api/[controller]")]     [ApiController]     public class AuthenorizationController : ControllerBase     {          JwtConfig _jwtconfig;         public AuthenorizationController(IOptions<JwtConfig> jwtconfig)          {             _jwtconfig = jwtconfig.Value;         }          [HttpGet]         public async Task<string> GetToken(string userName, string passWord)         {             string token = JwtHeleper.GetToken(new()             {                 UserName = userName,                 Extended1 = "无信息",                 Role = new List<RoleInfo>()                 {                     new RoleInfo() { Id = "1",Role="系统管理员"} ,                     new RoleInfo() { Id = "2",Role="用户管理员"} ,                 }             }, new()             {                 Audience = _jwtconfig.Audience,                 Issuer = _jwtconfig.Issuer,                 SecurityKey = _jwtconfig.SecurityKey,                 ExpiresMinutes = 5,             });              await Task.CompletedTask;             return token;         }     } } 

JwtHeleper.cs

using AuthenorizationCenter.Tools.Model; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text;  namespace AuthenorizationCenter.Tools {     public class JwtHeleper     {         public static string GetToken(UserInfo user, JwtConfig jwtConfig)         {             List<Claim> claims = new List<Claim>             {                 new Claim(ClaimTypes.Name, user.UserName ?? ""),                 new Claim("Extended1", user.Extended1 ?? ""),                 new Claim("Extended2", user.Extended2 ?? ""),                 new Claim("Extended3", user.Extended3 ?? ""),                 new Claim("Extended4", user.Extended4 ?? ""),                 new Claim("Extended5", user.Extended5 ?? ""),             };             if (user.Role is not null)             {                 foreach (var item in user.Role)                 {                     claims.Add(new Claim(item.Id.ToString(), item.Role));                 }             }             if (jwtConfig.SecurityKey == null)             {                 throw new Exception("JwtConfig.SecurityKey 不能为空");             }             SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SecurityKey));             SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);             JwtSecurityToken token = new JwtSecurityToken(                 issuer: jwtConfig.Issuer,                 audience: jwtConfig.Audience,                 claims: claims,                 expires: DateTime.UtcNow.AddMinutes(jwtConfig.ExpiresMinutes),                 signingCredentials: creds             );             string resultToken = new JwtSecurityTokenHandler().WriteToken(token);             return resultToken;         }     } } 

RoleInfo.cs

namespace AuthenorizationCenter.Tools.Model {     public class RoleInfo     {         public string Id { get; set; }         public string Role { get; set; }     } } 

UserInfo.cs

namespace AuthenorizationCenter.Tools.Model {     public class UserInfo     {         public string? UserName { get; set; }         public List<RoleInfo>? Role { get; set; }         public string? Extended1 { get; set; }         public string? Extended2 { get; set; }         public string? Extended3 { get; set; }         public string? Extended4 { get; set; }         public string? Extended5 { get; set; }     } } 

appsetting.json

{   "JwtTokenOptions": {     "Issuer": "https://localhost:7117",     "Audience": "https://localhost:7117",     "SecurityKey": "kq4DY5N1eFJhscOkI7Zp4Nd0WNy9d9AEsN6Yjgdv9OxLyol66tzGBKT_7vwolN7GZ8EDwqJBwccjDJfb81ws5s3sbbP5wUzQ3-PcTSsD-Rueiu2rsOUZwg_NR3RBCwmtouV-832YV2trCjNTawLB1z0LMukWGFNaAJVZ8WdQcrYn6a0ko5oVhZqaHBgsCLEGiqPtoFsiCcrJTz1IvXHk9_cDSr2hwEmSl18GlkOtgCHFH8aidYth3aQHRHuClTi6Y9mYRJtqqK-FNQYq4ZP23DSGZGFejJFTnM9YMpppuTMLklhSGySwX8rfjZ_0L5ac18nHaykTaiC2fvH00W42qQ"   } } 

gRPC准备

Program.cs

JwtConfig jwtConfig = new JwtConfig(); builder.Configuration.Bind("JwtTokenOptions", jwtConfig);  builder.Services.AddAuthentication(options => {     options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => {     options.TokenValidationParameters = new TokenValidationParameters()     {         ValidateIssuer = true,         ValidIssuer = jwtConfig.Issuer, //发行人         ValidateAudience = true,         ValidAudience = jwtConfig.Audience,//订阅人         ValidateIssuerSigningKey = true,         //对称加密密钥         IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SecurityKey!)),         ValidateLifetime = true, //验证失效时间         ClockSkew = TimeSpan.FromSeconds(30), //过期时间容错值         RequireExpirationTime = true,         AudienceValidator = (audiences, securityToken, validationParameters) =>         {             return true;         },         LifetimeValidator = (notBefore, expires, securityToken, validationParameters) =>         {             return true;         }     }; });  builder.Services.AddTransient<IUserServices, UserServices>(); builder.Services.AddTransient<IAuthorizationHandler, JwtAuthorization>(); builder.Services.AddAuthorization(options => {     options.AddPolicy("JwtPolicy", policy =>     {         //jwt 授权         policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)         //这里为自定义授权指定一下类         .AddRequirements(new UserRoleRequirement(JwtBearerDefaults.AuthenticationScheme));     }); }); 

CustomGreeterService.cs

using Grpc.Core; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization;  namespace Custom.Service {     public class CustomGreeterService : CustomGreeter.CustomGreeterBase     {         [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme,Policy = "JwtPolicy")]         public override async Task<NumberResult> Plus(Number request, ServerCallContext context) =>              await Task.FromResult<NumberResult>(new NumberResult()             {                 Result = request.LeftNumber + request.RightNumber,             });     } } 

JwtAuthorization.cs

using Cnpc.Com.Ioc.IBll; using Microsoft.AspNetCore.Authorization; using System.Security.Claims;  namespace GrpcService.Authorization {     public class UserRoleRequirement : IAuthorizationRequirement     {         public string AuthenticateScheme;         public UserRoleRequirement(string authenticateScheme)         {             AuthenticateScheme = authenticateScheme;         }     }     public class JwtAuthorization : AuthorizationHandler<UserRoleRequirement>     {         IUserServices userSercices;         public JwtAuthorization(IUserServices userSercices)         {             this.userSercices = userSercices;         }         protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UserRoleRequirement requirement)         {             string? userName = context.User.FindFirst(it => it.Type == ClaimTypes.Name)?.Value;             if (userSercices.IsAdmin(userName!))             {                 context.Succeed(requirement);             }             else             {                 context.Fail();             }             return Task.CompletedTask;         }     } } 

客户端调用

Program.cs

builder.Services.AddGrpcClient<CustomGreeterClient>(options => {     options.Address = new Uri("https://localhost:7166"); }).AddInterceptor<LogInterceptor>().ConfigureChannel(async config => {     //所有调用自动添加 Authorization     CallCredentials credentials = CallCredentials.FromInterceptor(async (context, metadata) =>     {         string token = await HttpClientHelper.HttpGetAsync("https://localhost:7117/api/Authenorization?userName=admin&passWord=666");         metadata.Add("Authorization", $"Bearer {token}");     });     config.Credentials = ChannelCredentials.Create(new SslCredentials(), credentials); }); 

HttpClientHelper.cs

using Newtonsoft.Json; using System.Text;  namespace gRpcWebAPI.Utility {     public static class HttpClientHelper     {         public static async Task<string> HttpGetAsync(string url, string contentType = "application/json", Dictionary<string, string> headers = null)         {             using (System.Net.Http.HttpClient client = new System.Net.Http.HttpClient())             {                 if (contentType != null)                     client.DefaultRequestHeaders.Add("ContentType", contentType);                 if (headers != null)                 {                     foreach (var header in headers)                         client.DefaultRequestHeaders.Add(header.Key, header.Value);                 }                 HttpResponseMessage response = await client.GetAsync(url);                 return await response.Content.ReadAsStringAsync();             }         }     } } 

gRpcController.cs

using Custom.Service; using Grpc.Core; using gRpcWebAPI.Utility; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using System.Security.Cryptography.X509Certificates; using static Custom.Service.CustomGreeter;  namespace gRpcWebAPI.Controllers {     [Route("api/[controller]/[action]")]     [ApiController]     public class gRpcController : ControllerBase     {         CustomGreeterClient _client;         public gRpcController(CustomGreeterClient client)         {             _client = client;         }          [HttpGet]         public async Task<IActionResult> AsyncPlus(int leftNumber, int rightNumber)         {             try             {                 //string token = await HttpClientHelper.HttpGetAsync("https://localhost:7117/api/Authenorization?userName=admin&passWord=666");                 //Metadata jwtCode = new Metadata { { "Authorization", $"Bearer {token}" } };                 NumberResult number = await _client.PlusAsync(new Custom.Service.Number() { LeftNumber = leftNumber, RightNumber = rightNumber });                 return new JsonResult(number);             }             catch (Exception ex)             {                 return new JsonResult(ex.Message);             }         }          [HttpGet]         public IActionResult Plus(int leftNumber, int rightNumber)         {             try             {                 string token = HttpClientHelper.HttpGet("https://localhost:7117/api/Authenorization?userName=admin&passWord=666");                  Metadata jwtCode = new Metadata { { "Authorization",$"Bearer {token}"} };                 NumberResult number = _client.Plus(new Custom.Service.Number() { LeftNumber = leftNumber, RightNumber = rightNumber },headers: jwtCode);                 return new JsonResult(number);             }             catch (Exception ex)             {                 return new JsonResult(ex.Message);             }         }     } } 

Rpc 与 Restful 区别

  • RPC是以一种调用本地方法的思路来调用远程方法,通过各种RPC框架隐藏调用远程方法的细节,让用户以为调用的就是本地方法。RPC隐藏了底层网络通信的复杂度,让我们更专注于业务逻辑的开发。

  • REST通过HTTP实现,把用户的需求抽象成对资源的操作,用户必须通过HTTP协议的GET、HEAD、POST、PUT、DELETE、TRACE、OPTIONS七种基本操作去和服务器交互。

  • RPC通常是服务器和服务器之间的通信,比如和中间件的通信,MQ、分布式缓存、分布式数据库等等。

  • 而REST通常是面向客户端的(一般是浏览器),他们的使用场景也是不一样的。