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.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

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文件夹分别拷贝到GrpcServerGrpcClient下的\bin\Debug\Certs

6.2 启动程序

先运行GrpcServer在运行GrpcClient即可

6.3 调试

右键解决方案-->属性-->启动项目-->选择多个启动项目-->F5调试即可

7.源码地址

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

最新文章

  1. nginx+php的使用
  2. BZOJ 2115 【Wc2011】 Xor
  3. iOS开发——UI进阶篇(十九)UISearchBar控件简介
  4. Add Two Numbers LeetCode Java
  5. #一周五# win10通用平台,无处不在的Xamarin,msbuild开源,MVP卢建晖的Asp.NET 5系列 (视频)
  6. NGUI之添加响应函数
  7. 我的c++学习(2)比较两个数字大小
  8. 【POJ】2299 Ultra-QuickSort(逆序对)
  9. excel 批量替换换行符
  10. Protocol Buffers编码详解,例子,图解
  11. JavaScript判断IE各版本完美解决方案
  12. 04737_C++程序设计_第7章_类模板与向量
  13. falsk 项目中日志设置
  14. PHP--php时间差8个小时的问题
  15. CentOS配置SSH无密码
  16. day01-h1字体大小和文本居中
  17. wslgit
  18. Oracle 11.2.0.4.0 Dataguard部署和日常维护(3)-Datauard监控篇
  19. Centos7 Nginx 开机启动
  20. Azure 基础:File Storage

热门文章

  1. 基于python的数学建模---灰色与模糊问题
  2. 关于linux配置java环境变量问题
  3. PHP8.1.10手动安装教程及报错解决梳理
  4. MasaFramework -- 领域驱动设计
  5. Windows10下python3和python2同时安装(一)安装python3和python2
  6. 解读JVM级别本地缓存Caffeine青出于蓝的要诀 —— 缘何会更强、如何去上手
  7. 关于盒子动态高度与transition的问题
  8. Hexo+Gitee免费搭建静态博客
  9. PTA散列表平方探测法解决冲突
  10. 开局一张图,构建神奇的 CSS 效果