.Net Core(.Net6)创建grpc

1.环境要求

.Net6,Visual Studio 2019 以上

官方文档: https://learn.microsoft.com/zh-cn/aspnet/core/tutorials/grpc/grpc-start
Net Framework 版本: https://www.cnblogs.com/dennisdong/p/17119944.html

2.搭建帮助类

2.1 新建类库

GrpcCommon
.Net Core(.Net6)创建grpc

2.2 新建文件夹

文件夹:Certs,Helpers,Models

2.3 安装依赖

NuGet依赖包Microsoft.AspNetCore.Authentication.JwtBeare 6.0.12,Newtonsoft.Json 13.0.2
.Net Core(.Net6)创建grpc

2.4 新建项目文件

Models下新建JwtToken.csUserDetails.cs

namespace GrpcCommon.Models {     public class JwtToken     {         public string? UserId { get; set; }         public string? Exp { get; set; }         public string? Iss { get; set; }     } } 
namespace GrpcCommon.Models {     public class UserDetails     {         public string? UserName { get; set; }         public int Age { get; set; }         public IEnumerable<string>? Friends { get; set; }     } } 

Helpers下新建JwtHelper.cs

using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json;  namespace GrpcCommon.Helpers {     public class JwtHelper     {         /// <summary>         /// 颁发JWT Token         /// </summary>         /// <param name="securityKey"></param>         /// <param name="accountName"></param>         /// <returns></returns>         public static string GenerateJwt(string securityKey, string accountName)         {             var claims = new List<Claim>             {                 new Claim("userid", accountName)             };              //秘钥 (SymmetricSecurityKey 对安全性的要求,密钥的长度太短会报出异常)             var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey));             var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);             var jwt = new JwtSecurityToken(                 issuer: "https://ifcloud.com/zerotrust",                 claims: claims,                 expires: DateTime.Now.AddMinutes(1),                 signingCredentials: credentials);             var jwtHandler = new JwtSecurityTokenHandler();             var encodedJwt = jwtHandler.WriteToken(jwt);             return encodedJwt;         }          /// <summary>         /// 解析         /// </summary>         /// <param name="token"></param>         /// <param name="securityKey"></param>         /// <returns></returns>         public static Tuple<bool, string> ValidateJwt(string token, string securityKey)         {             try             {                 //对称秘钥                 SecurityKey key = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(securityKey));                 //校验token                 var validateParameter = new TokenValidationParameters()                 {                     ValidateAudience = false,                     ValidIssuer = "https://ifcloud.com/zerotrust",                     ValidateIssuerSigningKey = true,                     IssuerSigningKey = key,                     ClockSkew = TimeSpan.Zero//校验过期时间必须加此属性                 };                 var jwtToken = new JwtSecurityTokenHandler().ValidateToken(token, validateParameter, out _);                 var claimDic = new Dictionary<string, string>();                  foreach (var claim in jwtToken.Claims)                 {                     claimDic.TryAdd(claim.Type, claim.Value);                 }                  var payLoad = JsonConvert.SerializeObject(claimDic);                  return new Tuple<bool, string>(true, payLoad);             }             catch (SecurityTokenExpiredException expired)             {                 //token过期                 return new Tuple<bool, string>(false, expired.Message);             }             catch (SecurityTokenNoExpirationException noExpiration)             {                 //token未设置过期时间                 return new Tuple<bool, string>(false, noExpiration.Message);             }             catch (SecurityTokenException tokenEx)             {                 //表示token错误                 return new Tuple<bool, string>(false, tokenEx.Message);             }             catch (Exception err)             {                 // 解析出错                 Console.WriteLine(err.StackTrace);                 return new Tuple<bool, string>(false, err.Message);             }         }     } } 

3.生成SSL证书(可跳过)

3.1 下载安装openssl

参考文章:https://www.cnblogs.com/dingshaohua/p/12271280.html

3.2 生成证书密钥

GrpcCommonCerts下右键打开命令窗口输入openssl

genrsa -out key.pem 2048 

3.3 生成pem证书

req -new -x509 -key key.pem -out cert.pem -days 3650 

3.4 pem证书转换成pfx证书

pkcs12 -export -out cert.pfx -inkey key.pem -in cert.pem 

4.搭建grpc服务器

4.1 新建grpc服务

GrpcServer
.Net Core(.Net6)创建grpc

4.2 新建文件夹

文件夹:Protos及其子文件夹Google

4.3 下载google protobuf文件

https://github.com/protocolbuffers/protobuf/releases/download/v21.12/protoc-21.12-win64.zip
其他版本参考:https://github.com/protocolbuffers/protobuf/releases
下载不了的文章末尾有源码地址

下载解压后将includegoogleprotobuf中的所有文件放在Protos下的Google

.Net Core(.Net6)创建grpc

4.4 新建proto文件

Protos下新建文件example.proto

syntax = "proto3";  package example; import "Protos/Google/struct.proto";  option csharp_namespace = "GrpcExample";  service ExampleServer { 	// Unary 	rpc UnaryCall (ExampleRequest) returns (ExampleResponse);  	// Server streaming 	rpc StreamingFromServer (ExampleRequest) returns (stream ExampleResponse);  	// Client streaming 	rpc StreamingFromClient (stream ExampleRequest) returns (ExampleResponse);  	// Bi-directional streaming 	rpc StreamingBothWays (stream ExampleRequest) returns (stream ExampleResponse); }  message ExampleRequest { 	string securityKey = 1; 	string userId = 2; 	google.protobuf.Struct userDetail = 3; 	string token = 4; }  message ExampleResponse { 	int32 code = 1; 	bool result = 2; 	string message = 3; } 

4.5 编译生成Stub

GrpcServer项目右键编辑项目文件添加内容

<ItemGroup> 	<Protobuf Include="Protosexample.proto" GrpcServices="Server" /> </ItemGroup> 

4.6 添加ssl证书(可跳过)

修改Program.cs

builder.WebHost     .ConfigureKestrel(serviceOpt =>     {         var httpPort = builder.Configuration.GetValue<int>("port:http");         var httpsPort = builder.Configuration.GetValue<int>("port:https");         serviceOpt.Listen(IPAddress.Any, httpPort, opt => opt.UseConnectionLogging());         serviceOpt.Listen(IPAddress.Any, httpsPort, listenOpt =>         {             var enableSsl = builder.Configuration.GetValue<bool>("enableSsl");             if (enableSsl)             {                 listenOpt.UseHttps("Certs\cert.pfx", "1234.com");             }             else             {                 listenOpt.UseHttps();             }              listenOpt.UseConnectionLogging();         });     }); 

修改appsettings.json,添加配置项

  "port": {     "http": 5000,     "https": 7000   },   "enableSsl": true 

4.7 新建服务类

ExampleService

using Grpc.Core; using GrpcCommon.Helpers; using GrpcCommon.Models; using GrpcExampleServer; using Newtonsoft.Json;  namespace GrpcServer.Services {     public class ExampleService : ExampleServer.ExampleServerBase     {         private readonly ILogger<ExampleService> _logger;          public ExampleService(ILogger<ExampleService> logger)         {             _logger = logger;         }          public override Task<ExampleResponse> UnaryCall(ExampleRequest request, ServerCallContext context)         {             Console.WriteLine(request.ToString());             _logger.LogInformation(request.ToString());             var tokenRes = JwtHelper.ValidateJwt(request.Token, request.SecurityKey);              // 正常响应客户端一次             ExampleResponse result;              if (tokenRes.Item1)             {                 var payLoad = JsonConvert.DeserializeObject<JwtToken>(tokenRes.Item2);                 if (payLoad == null)                 {                     result = new ExampleResponse                     {                         Code = -1,                         Result = false,                         Message = "payLoad为空"                     };                 }                 else                 {                     if (!request.UserId.Equals(payLoad.UserId))                     {                         result = new ExampleResponse                         {                             Code = -1,                             Result = false,                             Message = "userid不匹配"                         };                     }                     else                     {                         var userDetail = JsonConvert.DeserializeObject<UserDetails>(request.UserDetail.Fields.ToString());                         result = new ExampleResponse                         {                             Code = 200,                             Result = true,                             Message = $"UnaryCall 单次响应: {request.UserId},{userDetail?.UserName}"                         };                     }                 }             }             else             {                 // 正常响应客户端一次                 result = new ExampleResponse                 {                     Code = -1,                     Result = false,                     Message = tokenRes.Item2                 };             }             return Task.FromResult(result);         }          public override async Task StreamingFromServer(ExampleRequest request, IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)         {             // 无限响应客户端             while (!context.CancellationToken.IsCancellationRequested)             {                 await responseStream.WriteAsync(new ExampleResponse                 {                     Code = 200,                     Result = true,                     Message = $"StreamingFromServer 无限响应: {Guid.NewGuid()}"                 });                 await Task.Delay(TimeSpan.FromSeconds(3), context.CancellationToken);             }         }          public override async Task<ExampleResponse> StreamingFromClient(IAsyncStreamReader<ExampleRequest> requestStream, ServerCallContext context)         {             // 处理请求             await foreach (var req in requestStream.ReadAllAsync())             {                 Console.WriteLine(req.UserId);             }              // 响应客户端             return new ExampleResponse             {                 Code = 200,                 Result = true,                 Message = $"StreamingFromClient 单次响应: {Guid.NewGuid()}"             };         }          public override async Task StreamingBothWays(IAsyncStreamReader<ExampleRequest> requestStream, IServerStreamWriter<ExampleResponse> responseStream, ServerCallContext context)         {             // 服务器响应客户端一次             // 处理请求             //await foreach (var req in requestStream.ReadAllAsync())             //{             //    Console.WriteLine(req.UserName);             //}              // 请求处理完成之后只响应一次             //await responseStream.WriteAsync(new ExampleResponse             //{             //    Code = 200,             //    Result = true,             //    Message = $"StreamingBothWays 单次响应: {Guid.NewGuid()}"             //});             //await Task.Delay(TimeSpan.FromSeconds(3), context.CancellationToken);              // 服务器响应客户端多次             // 处理请求             var readTask = Task.Run(async () =>             {                 await foreach (var req in requestStream.ReadAllAsync())                 {                     Console.WriteLine(req.UserId);                 }             });              // 请求未处理完之前一直响应             while (!readTask.IsCompleted)             {                 await responseStream.WriteAsync(new ExampleResponse                 {                     Code = 200,                     Result = true,                     Message = $"StreamingBothWays 请求处理完之前的响应: {Guid.NewGuid()}"                 });                 await Task.Delay(TimeSpan.FromSeconds(3), context.CancellationToken);             }              // 也可以无限响应客户端             //while (!context.CancellationToken.IsCancellationRequested)             //{             //    await responseStream.WriteAsync(new ExampleResponse             //    {             //        Code = 200,             //        Result = true,             //        Message = $"StreamingFromServer 无限响应: {Guid.NewGuid()}"             //    });             //    await Task.Delay(TimeSpan.FromSeconds(3), context.CancellationToken);             //}         }     } }  

5.搭建grpc客户端

5.1 新建控制台程序

GrpcClient

5.2 拷贝文件夹

GrpcServer下的Protos拷贝一份到GrpcClient

5.3 安装依赖包

Google.Protobuf 3.21.12,Grpc.Net.Client 2.51.0,Grpc.Tools 2.51.0,Newtonsoft.Json 13.0.2
.Net Core(.Net6)创建grpc

5.4 编译生成Stub

GrpcServer项目右键编辑项目文件添加内容,注意这里是Client

<ItemGroup> 	<Protobuf Include="Protosexample.proto" GrpcServices="Client" /> </ItemGroup> 

5.5 新建测试类

ExampleTest.cs

using System.Security.Cryptography.X509Certificates; using Grpc.Net.Client; using Google.Protobuf.WellKnownTypes; using Grpc.Core; using GrpcCommon.Helpers; using GrpcExample;  namespace GrpcClient.Test {     internal class ExampleTest     {         public static void Run()         {             // 常规请求响应             UnaryCall();              // 服务器流响应             StreamingFromServer();              // 客户端流响应             StreamingFromClient();              // 双向流响应             StreamingBothWays();         }          /// <summary>         /// 创建客户端链接         /// </summary>         /// <param name="enableSsl"></param>         /// <returns></returns>         private static ExampleServer.ExampleServerClient CreateClient(bool enableSsl = true)         {             GrpcChannel channel;             if (enableSsl)             {                 const string serverUrl = "https://localhost:7000";                 Console.WriteLine($"尝试链接服务器,{serverUrl}");                  var handler = new HttpClientHandler();                 // 添加证书                 handler.ClientCertificates.Add(new X509Certificate2("Certs\cert.pfx", "1234.com"));                  // 忽略证书                 handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;                 channel = GrpcChannel.ForAddress(serverUrl, new GrpcChannelOptions                 {                     HttpClient = new HttpClient(handler)                 });             }             else             {                 const string serverUrl = "http://localhost:5000";                 Console.WriteLine($"尝试链接服务器,{serverUrl}");                 channel = GrpcChannel.ForAddress(serverUrl);             }              Console.WriteLine("服务器链接成功");             return new ExampleServer.ExampleServerClient(channel);         }          private static async void UnaryCall()         {             var client = CreateClient();             const string securityKey = "Dennis!@#$%^123456.com";             var userId = Guid.NewGuid().ToString();             var token = JwtHelper.GenerateJwt(securityKey, userId);             var result = await client.UnaryCallAsync(new ExampleRequest             {                 SecurityKey = securityKey,                 UserId = "Dennis",                 UserDetail = new Struct                 {                     Fields =                     {                         ["userName"] = Value.ForString("Dennis"),                         ["age"] = Value.ForString("18"),                         ["friends"] = Value.ForList(new Value                         {                             ListValue = new ListValue                             {                                 Values =                                 {                                     new List<Value>                                     {                                         Value.ForString("Roger"),                                         Value.ForString("YueBe")                                     }                                 }                             }                         })                     }                 },                 Token = token             });             Console.WriteLine($"Code={result.Code},Result={result.Result},Message={result.Message}");         }          private static async void StreamingFromServer()         {             var client = CreateClient();             var result = client.StreamingFromServer(new ExampleRequest             {                 UserId = "Dennis"             });              await foreach (var resp in result.ResponseStream.ReadAllAsync())             {                 Console.WriteLine($"Code={resp.Code},Result={resp.Result},Message={resp.Message}");             }         }          private static async void StreamingFromClient()         {             var client = CreateClient();             var result = client.StreamingFromClient();              // 发送请求             for (var i = 0; i < 5; i++)             {                 await result.RequestStream.WriteAsync(new ExampleRequest                 {                     UserId = $"StreamingFromClient 第{i}次请求"                 });                 await Task.Delay(TimeSpan.FromSeconds(1));             }              // 等待请求发送完毕             await result.RequestStream.CompleteAsync();              var resp = result.ResponseAsync.Result;             Console.WriteLine($"Code={resp.Code},Result={resp.Result},Message={resp.Message}");         }          private static async void StreamingBothWays()         {             var client = CreateClient();             var result = client.StreamingBothWays();              // 发送请求             for (var i = 0; i < 5; i++)             {                 await result.RequestStream.WriteAsync(new ExampleRequest                 {                     UserId = $"StreamingBothWays 第{i}次请求"                 });                 await Task.Delay(TimeSpan.FromSeconds(1));             }              // 处理响应             var respTask = Task.Run(async () =>             {                 await foreach (var resp in result.ResponseStream.ReadAllAsync())                 {                     Console.WriteLine($"Code={resp.Code},Result={resp.Result},Message={resp.Message}");                 }             });              // 等待请求发送完毕             await result.RequestStream.CompleteAsync();              // 等待响应处理             await respTask;         }     } } 

5.6 修改程序入口

Program.cs

using GrpcClient.Test; using Microsoft.Extensions.Hosting;  // Example测试 ExampleTest.Run();  Console.WriteLine("=================="); Console.WriteLine("按Ctrl+C停止程序"); Console.WriteLine("==================");  // 监听Ctrl+C await new HostBuilder().RunConsoleAsync(); 

6.运行项目

6.1 拷贝证书

把整个Certs文件夹分别拷贝到GrpcServerGrpcClient下的binDebugCerts

6.2 启动程序

先运行GrpcServer在运行GrpcClient即可

6.3 调试

右键解决方案-->属性-->启动项目-->选择多个启动项目-->F5调试即可
.Net Core(.Net6)创建grpc

7.源码地址

https://gitee.com/dennisdong/net-grpc

发表评论

评论已关闭。

相关文章