.Net Core(.Net6)创建grpc - DennisDong
source link: https://www.cnblogs.com/dennisdong/p/17120990.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
1.环境要求
.Net6
,Visual Studio 2019 以上
官方文档:
https://learn.microsoft.com/zh-cn/aspnet/core/tutorials/grpc/grpc-startNet Framework 版本:
https://www.cnblogs.com/dennisdong/p/17119944.html
2.搭建帮助类
2.1 新建类库
2.2 新建文件夹
文件夹:Certs
,Helpers
,Models
2.3 安装依赖
NuGet
依赖包Microsoft.AspNetCore.Authentication.JwtBeare 6.0.12
,Newtonsoft.Json 13.0.2
2.4 新建项目文件
在Models
下新建JwtToken.cs
和UserDetails.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 生成证书密钥
在GrpcCommon
的Certs
下右键打开命令窗口输入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服务
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
下载不了的文章末尾有源码地址
下载解压后将\include\google\protobuf
中的所有文件放在Protos
下的Google
中
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="Protos\example.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
5.4 编译生成Stub
GrpcServer
项目右键编辑项目文件
添加内容,注意这里是Client
<ItemGroup> <Protobuf Include="Protos\example.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
文件夹分别拷贝到GrpcServer
和GrpcClient
下的\bin\Debug\Certs
6.2 启动程序
先运行GrpcServer
在运行GrpcClient
即可
6.3 调试
右键解决方案-->属性-->启动项目-->选择多个启动项目-->F5调试即可
7.源码地址
https://gitee.com/dennisdong/net-grpc
__EOF__
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK