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

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服务
GrpcServer

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中

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

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文件夹分别拷贝到GrpcServer和GrpcClient下的binDebugCerts
6.2 启动程序
先运行GrpcServer在运行GrpcClient即可
6.3 调试
右键解决方案-->属性-->启动项目-->选择多个启动项目-->F5调试即可
