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. Ant Design Pro:Layout 组件——嵌套布局

    在   BasicLayout.jsx   文件中修改 <ProLayout layout="topmenu" className="chenshuai2144&q ...

  2. 深度解析KubeEdge EdgeMesh 高可用架构

    摘要:通过高可用特性应用场景.高可用特性使用手册.课题总结.未来展望等四个部分的内容来向大家介绍新版本EdgeMesh的高可用架构. 本文分享自华为云社区<KubeEdge EdgeMesh 高 ...

  3. JDK 8 Stream 流 用 法

    import com.entity.Person;import org.junit.Test;import java.util.*;import java.util.function.Function ...

  4. 腾讯云数据库SaaS致力于构建数据库分布式云,为更多更广的用户提供服务

    大数据时代,数据库 SaaS 是企业实现降本增效和业务创新的重要抓手.在腾讯全球数字生态大会数据库 SaaS 专场上,腾讯云发布了多项数据库 SaaS 产品能力升级,并重点分享了其在上云.日常运维.数 ...

  5. 【大数据面试】Flink 01 概述:包含内容、层次架构、运行组件、部署模式、任务提交流程、任务调度概念、编程模型组成

    一.概述 1.介绍 对无界和有界数据流进行有状态计算的分布式引擎和框架,并可以使用高层API编写分布式任务,主要包括: DataSet API(批处理):静态数据抽象为分布式数据集,方便使用操作符进行 ...

  6. 在OpenEuler22.09(也适用于CentOS7+)编译安装Python3.9,并于原Python共存-指南

    指南使用操作系统:OpenEuler 22.09(网络安装,最小安装,使用默认源) 指南使用系统自带Python版本:3.10(高版本,这不是3.1喔) 1. 安装基本的编译环境 yum -y ins ...

  7. MySQL进阶实战7,查询的执行过程

    @ 目录 一.拆分查询 二.分解关联查询 三.查询的执行过程 四.优化器的一些优化手段 1.重新定义关联表的顺序 2.将外连接转化为内连接 3.使用增加变换规则 4.优化count().max().m ...

  8. RocketMQ Connect 构建流式数据处理平台

    本文作者:孙晓健,Apache RocketMQ Committer 01 RocketMQ Connect RocketMQ Connect 是一款可扩展的在 RocketMQ 与其他系统之间做流式 ...

  9. Vue 响应式原理模拟以及最小版本的 Vue的模拟

    在模拟最小的vue之前,先复习一下,发布订阅模式和观察者模式 对两种模式有了了解之后,对Vue2.0和Vue3.0的数据响应式核心原理 1.Vue2.0和Vue3.0的数据响应式核心原理 (1).  ...

  10. java中对象存在形式

    本文主要讲述jvm中对象的存储形式: class Cat{ String name; int age; String color; // 行为 } 依据Cat类创建对象 public class Ob ...