1.环境要求

.Net Framework 4.8

.Net Core 版本: https://www.cnblogs.com/dennisdong/p/17120990.html

2.Stub和Proto

2.1 新建类库

GrpcCommon

2.2 新建文件夹和配置文件

文件夹:Certs,Helpers,Models,Protos\Google,Stubs\Example

class类:AppConfigs.cs

using System;
using System.Configuration; namespace GrpcCommon
{
public class AppConfigs
{
public static string Host = GetValue("host");
public static int HttpPort = Convert.ToInt32(GetValue("httpPort"));
public static int HttpsPort = Convert.ToInt32(GetValue("httpsPort"));
public static string Issuer = GetValue("issuer");
public static int Expire = Convert.ToInt32(GetValue("expire"));
public static string SecurityKey = GetValue("securityKey"); public static string GetValue(string key)
{
try
{
return ConfigurationManager.AppSettings[key].Trim();
}
catch (Exception e)
{
throw new Exception($"AppConfig 配置获取异常,{e.StackTrace}");
}
} public static T GetValue<T>(string key) where T : class
{
try
{
return ConfigurationManager.AppSettings[key].Trim() as T;
}
catch (Exception e)
{
throw new Exception($"AppConfig 配置获取异常,{e.StackTrace}");
}
}
}
}

添加程序集引用:System.Configuration

2.3 新建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;
}

2.4 下载google的数据类型文件

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

2.5 NuGet安装依赖包

GrpcCommon右键管理NuGet程序包安装以下依赖:

Google.Protobuf 3.21.12,Grpc.Core 2.46.6,Grpc.Tools 2.51.0,JWT 10.0.2

2.6 生成stub文件

项目根目录下按下Shift+鼠标右键在此打开命令窗口

.\packages\Grpc.Tools.2.51.0\tools\windows_x64\protoc.exe -I .\GrpcCommon\ .\GrpcCommon\Protos\example.proto --csharp_out .\GrpcCommon\Stubs\Example --grpc_out .\GrpcCommon\Stubs\Example --plugin=protoc-gen-grpc=.\packages\Grpc.Tools.2.51.0\tools\windows_x64\grpc_csharp_plugin.exe

运行之后会在Example文件夹下生成Example.csExampleGrpc.cs

2.7 配置JWT

2.7.1 在Models下新建JwtToken

/// <summary>
/// Jwt Token
/// </summary>
public class JwtToken
{
/// <summary>
/// 授权者
/// </summary>
public string userid { get; set; } /// <summary>
/// Token过期时间
/// </summary>
public long exp { get; set; } /// <summary>
/// Issuer
/// </summary>
public string iss { get; set; }
}

2.7.2 在Models下新建UserDetails

public class UserDetails
{
public string UserName { get; set; }
public int Age { get; set; }
public IEnumerable<string> Friends { get; set; }
}

2.7.2 在Helpers下新建JwtHelper

using System;
using System.Text;
using GrpcCommon.Models;
using JWT;
using JWT.Algorithms;
using JWT.Exceptions;
using JWT.Serializers; #pragma warning disable CS0618 namespace GrpcCommon.Helpers
{
public class JwtHelper
{
/// <summary>
/// 颁发JWT Token
/// </summary>
/// <param name="securityKey"></param>
/// <param name="userName"></param>
/// <returns></returns>
public static string GenerateJwt(string securityKey, string userName)
{
//var securityKey = AppConfigs.SecurityKey;
var issuer = AppConfigs.Issuer;
var expire = AppConfigs.Expire;
var expTime = new DateTimeOffset(DateTime.Now.AddSeconds(expire)).ToUnixTimeSeconds(); //身份验证信息
var jwtToken = new JwtToken { userid = userName, exp = expTime, iss = issuer };
var key = Encoding.UTF8.GetBytes(securityKey);
IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); //加密方式
IJsonSerializer serializer = new JsonNetSerializer(); //序列化Json
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); //base64加解密
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder); //JWT编码
var token = encoder.Encode(jwtToken, key); //生成令牌 return token;
} /// <summary>
/// 校验解析Jwt Token
/// </summary>
/// <returns></returns>
public static Tuple<bool, string> ValidateJwt(string token, string secret)
{
try
{
IJsonSerializer serializer = new JsonNetSerializer();
IDateTimeProvider provider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, provider);
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtAlgorithm alg = new HMACSHA256Algorithm();
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, alg);
var payLoad = decoder.Decode(token, secret); //校验通过,返回解密后的字符串
return new Tuple<bool, string>(true, payLoad);
}
catch (TokenExpiredException expired)
{
//token过期
return new Tuple<bool, string>(false, expired.Message);
}
catch (SignatureVerificationException sve)
{
//签名无效
return new Tuple<bool, string>(false, sve.Message);
}
catch (Exception err)
{
// 解析出错
Console.WriteLine(err.StackTrace);
return new Tuple<bool, string>(false, err.Message);
}
}
}
}

3.搭建grpc服务端

3.1 新建控制台应用程序

GrpcServer

3.2 安装依赖包

Google.Protobuf 3.21.12,Grpc.Core 2.46.6,Newtonsoft.Json 13.0.2

3.3 新建服务类

ExampleService.cs

using System;
using System.Threading.Tasks;
using Grpc.Core;
using GrpcCommon.Helpers;
using GrpcCommon.Models;
using GrpcExample;
using Newtonsoft.Json; namespace GrpcServer.Services
{
public class ExampleService : ExampleServer.ExampleServerBase
{
public override Task<ExampleResponse> UnaryCall(ExampleRequest request, ServerCallContext context)
{
Console.WriteLine(request.ToString());
var tokenRes = JwtHelper.ValidateJwt(request.Token, request.SecurityKey); // 正常响应客户端一次
ExampleResponse result; if (tokenRes.Item1)
{
var payLoad = JsonConvert.DeserializeObject<JwtToken>(tokenRes.Item2); 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)
{
// 处理请求
while (await requestStream.MoveNext())
{
Console.WriteLine(requestStream.Current.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)
{
#region 服务器响应客户端一次 // 处理请求
//while (await requestStream.MoveNext())
//{
// Console.WriteLine(requestStream.Current.UserName);
//} //请求处理完成之后只响应一次
//await responseStream.WriteAsync(new ExampleResponse
//{
// Code = 200,
// Result = true,
// Message = $"StreamingBothWays 单次响应: {Guid.NewGuid()}"
//});
//await Task.Delay(TimeSpan.FromSeconds(3), context.CancellationToken); #endregion #region 服务器响应客户端多次 // 处理请求
var readTask = Task.Run(async () =>
{
while (await requestStream.MoveNext())
{
Console.WriteLine(requestStream.Current.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);
//} #endregion
}
}
}

3.4 AppConfig添加配置

<appSettings>
<!--主机配置-->
<add key="host" value="0.0.0.0" />
<add key="httpPort" value="5000" />
<add key="httpsPort" value="7000" />
<!--Jwt配置-->
<add key="securityKey" value="grpc.dennis.com" />
<add key="issuer" value="https://grpc.dennis.com" />
<!--token过期时间:分钟-->
<add key="expire" value="1" />
</appSettings>

3.5 修改程序入口

修改Program.cs,SSL证书文章后面有说明

using System;
using Grpc.Core;
using System.Collections.Generic;
using System.IO;
using GrpcCommon;
using GrpcExample;
using GrpcServer.Services; private static void Main()
{
var host = AppConfigs.Host;
var httpPort = AppConfigs.HttpPort;
var httpsPort = AppConfigs.HttpsPort;
var cert = File.ReadAllText("Certs\\cert.pem");
var key = File.ReadAllText("Certs\\key.pem");
var server = new Server
{
Services =
{
ExampleServer.BindService(new ExampleService())
},
Ports =
{
new ServerPort(host, Convert.ToInt32(httpPort), ServerCredentials.Insecure),
new ServerPort(host, Convert.ToInt32(httpsPort), new SslServerCredentials(
new List<KeyCertificatePair>
{
new KeyCertificatePair(cert, key)
}))
}
};
server.Start(); Console.WriteLine($"Grpc Server Listening on http://{host}:{httpPort}, https://{host}:{httpsPort}");
Console.ReadLine(); server.ShutdownAsync().Wait();
}

4.搭建grpc客户端

4.1 新建控制台应用程序

GrpcClient

4.2 安装依赖包

Google.Protobuf 3.21.12,Grpc.Core 2.46.6,Newtonsoft.Json 13.0.2

4.3 新建测试类

ExampleUnit.cs

using System;
using Google.Protobuf.WellKnownTypes;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Grpc.Core;
using GrpcExample;
using GrpcCommon.Helpers; namespace GrpcClient.Test
{
internal class ExampleUnit
{
public static void Run()
{
// 常规请求响应
UnaryCall(); // 服务器流响应
StreamingFromServer(); // 客户端流响应
StreamingFromClient(); // 双向流响应
StreamingBothWays();
} /// <summary>
/// 创建客户端链接
/// </summary>
/// <param name="enableSsl"></param>
/// <returns></returns>
private static ExampleServer.ExampleServerClient CreateClient(bool enableSsl = true)
{
Channel channel;
if (enableSsl)
{
// ssl加密连接
const string serverUrl = "localhost:7000";
Console.WriteLine($"尝试链接服务器,https://{serverUrl}");
var credentials = new SslCredentials(File.ReadAllText("Certs\\cert.pem"));
channel = new Channel(serverUrl, credentials, new List<ChannelOption>
{
new ChannelOption(ChannelOptions.SslTargetNameOverride, "grpc.dennis.com")
});
}
else
{
// 不安全连接
const string serverUrl = "localhost:5000";
Console.WriteLine($"尝试链接服务器,http://{serverUrl}");
channel = new Channel(serverUrl, ChannelCredentials.Insecure);
} Console.WriteLine("服务器链接成功");
return new ExampleServer.ExampleServerClient(channel);
} private static async void UnaryCall()
{
var client = CreateClient();
var userId = Guid.NewGuid().ToString();
const string securityKey = "Dennis!@#$%^";
var token = JwtHelper.GenerateJwt(securityKey, userId); var result = await client.UnaryCallAsync(new ExampleRequest
{
SecurityKey = securityKey,
UserId = userId,
UserDetail = new Struct
{
Fields =
{
["userName"] = Value.ForString("Dennis"),
["age"] = Value.ForString("18"),
["friends"] = Value.ForList(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"
}); while (await result.ResponseStream.MoveNext())
{
var resp = result.ResponseStream.Current;
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 () =>
{
while (await result.ResponseStream.MoveNext())
{
var resp = result.ResponseStream.Current;
Console.WriteLine($"Code={resp.Code},Result={resp.Result},Message={resp.Message}");
}
}); // 等待请求发送完毕
await result.RequestStream.CompleteAsync(); // 等待响应处理
await respTask;
}
}
}

4.4 添加配置信息

<appSettings>
<!--主机配置-->
<add key="host" value="0.0.0.0" />
<add key="httpPort" value="5000" />
<add key="httpsPort" value="7000" />
<!--Jwt配置-->
<add key="securityKey" value="grpc.dennis.com" />
<add key="issuer" value="https://grpc.dennis.com" />
<!--token过期时间:分钟-->
<add key="expire" value="10" />
</appSettings>

4.5 修改程序入口

修改Program.cs

using System;
using GrpcClient.Test; namespace GrpcClient
{
internal class Program
{
static void Main(string[] args)
{
// Example 测试
ExampleUnit.Run(); Console.ReadKey();
}
}
}

5.SSL证书生成

5.1 下载安装openssl

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

5.2 生成证书

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

5.2.1 生成key

genrsa -out key.pem 2048

5.2.1 生成pem证书

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

5.2.1 pem证书转换成pfx证书

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

6.运行项目

6.1 拷贝证书

把整个Certs文件夹分别拷贝到GrpcServerGrpcClient下的\bin\Debug\Certs

6.2 启动程序

先运行GrpcServer在运行GrpcClient即可

6.3 调试

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

7.源码地址

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

.Net Framework创建grpc的更多相关文章

  1. unit vs2017基于nunit framework创建单元测试

    unit  vs2017基于nunit framework创建单元测试 一.简叙: 单元测试大型项目中是必备的,所以不可忽视,一个项目的成败就看是否有单元测试,对后期的扩展维护都带来了便利. 二.安装 ...

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

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

  3. iOS静态库及Framework 创建

    本文转自cocoachina,尊重作者的汗水. 讲述的非常透彻,有需要的朋友可以阅读实践.转载请注明出处 //=================以下留着备份==================// 在 ...

  4. Asp.Net MVC 使用Entity Framework创建模型类

    先来说说LINQ to SQL和Entity Framework的区别: LINQ to SQL和Entity Framework都是一种包含LINQ功能的对象关系映射技术.他们之间的本质区别在于EF ...

  5. Asp.Net MVC 模型(使用Entity Framework创建模型类) - Part.1

    这篇教程的目的是解释在创建ASP.NET MVC应用程序时,如何使用Microsoft Entity Framework来创建数据访问类.这篇教程假设你事先对Microsoft Entity Fram ...

  6. Asp.Net MVC 模型(使用Entity Framework创建模型类)

    这篇教程的目的是解释在创建ASP.NET MVC应用程序时,如何使用Microsoft Entity Framework来创建数据访问类.这篇教程假设你事先对Microsoft Entity Fram ...

  7. .NET Core/Framework 创建委托以大幅度提高反射调用的性能

    都知道反射伤性能,但不得不反射的时候又怎么办呢?当真的被问题逼迫的时候还是能找到解决办法的. 为反射得到的方法创建一个委托,此后调用此委托将能够提高近乎直接调用方法本身的性能.(当然 Emit 也能够 ...

  8. 使用ionic framework创建一个简单的APP

    ionic是一个以cordova为基础的html5前端框架,功能强大,能够快速做出与原生开发相似的应用. 一,安装和配置 1,安装(前提:cordova环境配置完成) npm install -g i ...

  9. 使用AssetsLibrary.Framework创建多图片选择控制器(翻译)

    系统的UIImagePickerController只能让用户选择单图片,而一般情况下,我们需要上传多张图片,这时应该可以同时选择多张图片,否则用户体验会很差.因此多图片选择器就诞生了. 在类库中,苹 ...

  10. .NET Core 3.0中用 Code-First 方式创建 gRPC 服务与客户端

    .NET Core love gRPC 千呼万唤的 .NET Core 3.0 终于在 9 月份正式发布,在它的众多新特性中,除了性能得到了大大提高,比较受关注的应该是 ASP.NET Core 3. ...

随机推荐

  1. 2022春每日一题:Day 33

    题目:[USACO 6.1.3] Cow XOR 没找到这题具体网址,这个题就是求最大异或区间(总长度尽量小,右端点尽量大) 嗯很显然一个[l,r]的异或和=s[r]s[l-1],那么现在有了优秀的n ...

  2. SolidWorks2020下载安装中文版教程,你solidworks安装失败是什么原因?

    SW2020 WIN10 64位安装步骤: 1.先使用"百度网盘客户端"下载SW20S5_CN_x64安装包到电脑磁盘英文路径文件夹里,并鼠标右击进行解压缩,安装前先断开电脑网络, ...

  3. .NET实现堆排序

    堆排序及相关知识 堆排序 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序.首先简单了解下堆结构. 堆 堆是具 ...

  4. Go语言核心36讲07

    在前文中,我解释过代码块的含义.Go语言的代码块是一层套一层的,就像大圆套小圆. 一个代码块可以有若干个子代码块:但对于每个代码块,最多只会有一个直接包含它的代码块(后者可以简称为前者的外层代码块). ...

  5. 1、Docker最新入门教程-Docker概述

    1. Docker概述 Docker 是一个用于开发.运输和运行应用程序的开放平台.Docker 使您能够将应用程序与基础架构分开,以便您可以快速交付软件.使用 Docker,您可以像管理应用程序一样 ...

  6. python选课系统项目详解

    选课系统项目详解 选课系统简介及分析 选课系统架构设计分析 选课系统目录设计 管理员视图 注册 登录 创建学校 创建课程 创建讲师 学生视图 注册 登录 选择学校 选择课程 查看分数 教师视图 登录 ...

  7. 在CentOS编译Git源码

    Git 是一个免费的开源分布式版本控制系统,旨在处理从小到小到的所有内容 具有速度和效率的超大型项目. Git易于学习,占用空间很小,性能快如闪电. 它超越了Subversion,CVS,Perfor ...

  8. js 中常用函数汇总(含示例)

    〇.前言 js 在日常开发中还是比较常用的,本文将常用的 js 方法简单汇总一下,希望对你我有一点帮助. 一.重复 / 延迟操作 1.设置固定时间间隔,重复执行(setInterval(funcRef ...

  9. JavaScript:七大基础数据类型:布尔值boolean、空null、未定义undefined

    布尔值boolean 没什么好说的,同其他编程语言一样,就两个值:true 和 false: 空null JS的null,和Java等编程语言的概念不一样,它不是一个"不存在的对象" ...

  10. 万字长文详解 YOLOv1-v5 系列模型

    一,YOLOv1 Abstract 1. Introduction 2. Unified Detectron 2.1. Network Design 2.2 Training 2.4. Inferen ...