- A+
所属分类:.NET技术
简介
-
高性能、开源的通用 RPC 框架
-
实现不同语言相互调用
创建gRPC
创建服务端
- vs2022直接搜索grpc默认下一步创建
创建控制台测试
- 创建控制台
- 引入以下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">
- 打开服务端.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.选中客户端项目右键 -> 调试 -> 启动新实例 即可两个项目全部命中断点进行调试测试
创建自定义服务
-
服务端
- 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; }
- 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(); - Protos文件夹添加 custom.proto文件并添加下列代码,重新生成项目打开项目所在文件夹,打开路径:objDebugnet6.0Protos 查看CustomGrpc.cs是否存在,如果没有存在则在.csproj文件中添加:
-
客户端
- 将服务端的custom.proto文件拷贝到Protos文件夹内并在.csproj文件中添加(注意这里GrpcServices="Client"):
- 重新生成项目并检查路径: objDebugnet6.0Protos 是否生成对应的CustomGrpc.cs文件
- 加入测试代码:
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); } }
- 测试步骤与上面控制台测试一样,调试方式也一样
- 将服务端的custom.proto文件拷贝到Protos文件夹内并在.csproj文件中添加(注意这里GrpcServices="Client"):
服务器流式处理方法
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
- 服务端, 客户端 都需要继承 Interceptor
- 重新需要实现Aop的方法,如服务端: UnaryServerHandler,客户端: AsyncUnaryCall 针对一元调用的Aop
- 我这里使用的是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验证
- 准备单独的一个网站发布jwt Token (授权中心)
- 然后在gRPC项目中jwt鉴权 和 获取到角色信息之后授权
- webapi测试gRPC调用, 先在授权中心获取token然后在 ConfigureChannel 方法中设置gRPC全局的jwt token
- 返回401: jwt鉴权不通过
- 返回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通常是面向客户端的(一般是浏览器),他们的使用场景也是不一样的。