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

.Net Core(.Net6)创建grpc的更多相关文章

  1. .NET Core(.NET6)中gRPC使用

    一.简介 简单解析一下gRPC,gRPC 是一个由Google开源的,跨语言的,高性能的远程过程调用(RPC)框架. 特点: 跨语言 内容protobuf格式(比json体积小),网络传输快 使用HT ...

  2. [gRPC] 在 .NET Core 中创建 gRPC 服务端和客户端

    gRPC 官网:https://grpc.io/ 1. 创建服务端 1.1 基于 ASP.NET Core Web 应用程序模板创建 gRPC Server 项目. 1.2 编译并运行 2. 创建客户 ...

  3. .NET Core(.NET6)中gRPC注册到Consul

    一.简介 上一篇文章介绍了.NET Core 中使用gRPC,在微服务中,我们通常要把服务做成服务注册,服务发现的方式,那么这里来说一下gRPC是如何注册到Consul中的. Consul的安装这里就 ...

  4. ASP.NET Core 3.0 gRPC 双向流

    目录 ASP.NET Core 3.0 使用gRPC ASP.NET Core 3.0 gRPC 双向流 ASP.NET Core 3.0 gRPC 认证授权 一.前言 在前一文 <ASP.NE ...

  5. ASP.NET Core 3.0 gRPC 配置使用HTTP

    前言 gRPC是基于http/2,是同时支持https和http协议的,我们在gRPC实际使用中,在内网通讯场景下,更多的是走http协议,达到更高的效率,下面介绍如何在 .NET Core 3.0 ...

  6. .Net Core中使用Grpc

    一.Grpc概述 gRPC 基于如下思想:定义一个服务, 指定其可以被远程调用的方法及其参数和返回类型.gRPC 默认使用protocol buffers作为接口定义语言,来描述服务接口和有效载荷消息 ...

  7. 002.Create a web API with ASP.NET Core MVC and Visual Studio for Windows -- 【在windows上用vs与asp.net core mvc 创建一个 web api 程序】

    Create a web API with ASP.NET Core MVC and Visual Studio for Windows 在windows上用vs与asp.net core mvc 创 ...

  8. 使用 ASP.NET Core MVC 创建 Web API(五)

    使用 ASP.NET Core MVC 创建 Web API 使用 ASP.NET Core MVC 创建 Web API(一) 使用 ASP.NET Core MVC 创建 Web API(二) 使 ...

  9. 使用 ASP.NET Core MVC 创建 Web API(二)

    使用 ASP.NET Core MVC 创建 Web API 使用 ASP.NET Core MVC 创建 Web API(一) 六.添加数据库上下文 数据库上下文是使用Entity Framewor ...

  10. 使用 ASP.NET Core MVC 创建 Web API(三)

    使用 ASP.NET Core MVC 创建 Web API 使用 ASP.NET Core MVC 创建 Web API(一) 使用 ASP.NET Core MVC 创建 Web API(二) 十 ...

随机推荐

  1. beego-orm-sqlite

    dao.go ``` package daoimport ( "fmt" "github.com/astaxie/beego/orm" "html&q ...

  2. 编码工具使用(go语言)

    1.课程介绍 Git基础课程和实操 Goland介绍以及常用快捷键使用 Go delve 调试 你想要的linux 这里都有 2.版本控制工具介绍 原始的版本控制 修改文件,保存文件副本 版本控制的起 ...

  3. JAVA 用分苹果来理解本题

    思路 其实这是一道非常经典的分苹果问题:有m个一样的苹果和n个一样的盘子,把苹果放盘子里,每个盘子允许0-m个苹果,求问有多少种分法? 与本题的共通之点在于,输入的正整数可以看成m个苹果,拆分出的加数 ...

  4. 基于python的数学建模---洛伦兹线与数值解

    import numpy as np from scipy.integrate import odeint from mpl_toolkits.mplot3d import Axes3D import ...

  5. Go DevOps大厂运维平台开发进阶实战营

    使用 Jenkinsfile 创建流水线已报名老男孩运维课,见底下评论.enkinsfile 是一个文本文件,它包含 Jenkins 流水线的定义,并被检入源代码控制仓库.Jenkinsfile 将整 ...

  6. 将git仓库从submodule转换为subtree

    三个脚本 Alexander Mikhailian cat .gitmodules |while read i do if [[ $i == \[submodule* ]]; then mpath=$ ...

  7. [0x12] 135.最大子序和【单调队列】

    我在知乎上看到一句话,如一道晴天霹雳: "如果一个选手比你小还比你强,你就可以退役了."--单调队列的原理 题意 link(more:P1714) 给定一个长度为 \(n\) 的整 ...

  8. 47.DRF实现分页

    分页Pagination   当我们在PC 或者 App 有大量数据需要展示时,可以对数据进行分页展示.这时就用到了分页功能,分页使得数据更好的展示给用户   比如我们有1W+数据要返回给前端,数据量 ...

  9. java反射基础知识整理

    目录 1.反射机制的作用 2.获取一个类的实例 3.使用Class.forName()方法加载类的静态代码块 4.获取配置文件的路径 5.java反编译 5.1.获取类中的成员变量 5.2.通过类名反 ...

  10. [seaborn] seaborn学习笔记11-绘图实例(3) Drawing example(3)

    11 绘图实例(3) Drawing example(3)(代码下载) 本文主要讲述seaborn官网相关函数绘图实例.具体内容有: Plotting a diagonal correlation m ...