7

.Net Core(.Net6)创建grpc - DennisDong

 1 year ago
source link: https://www.cnblogs.com/dennisdong/p/17120990.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

2078491-20230214210455876-264355111.png

2.2 新建文件夹

文件夹:Certs,Helpers,Models

2.3 安装依赖

NuGet依赖包Microsoft.AspNetCore.Authentication.JwtBeare 6.0.12,Newtonsoft.Json 13.0.2

2078491-20230214211132682-1996111301.png

2.4 新建项目文件

Models下新建JwtToken.csUserDetails.cs

highlighter- arduino
namespace GrpcCommon.Models
{
    public class JwtToken
    {
        public string? UserId { get; set; }
        public string? Exp { get; set; }
        public string? Iss { get; set; }
    }
}
highlighter- arduino
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

highlighter- csharp
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

highlighter- sas
genrsa -out key.pem 2048

3.3 生成pem证书

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

3.4 pem证书转换成pfx证书

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

4.搭建grpc服务器

4.1 新建grpc服务

GrpcServer

2078491-20230214201359755-1913084663.png

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

2078491-20230214164847835-1891168737.png

4.4 新建proto文件

Protos下新建文件example.proto

highlighter- abnf
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项目右键编辑项目文件添加内容

highlighter- xml
<ItemGroup>
	<Protobuf Include="Protos\example.proto" GrpcServices="Server" />
</ItemGroup>

4.6 添加ssl证书(可跳过)

修改Program.cs

highlighter- pgsql
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,添加配置项

highlighter- yaml
  "port": {
    "http": 5000,
    "https": 7000
  },
  "enableSsl": true

4.7 新建服务类

ExampleService

highlighter- csharp
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

2078491-20230214213349789-402555083.png

5.4 编译生成Stub

GrpcServer项目右键编辑项目文件添加内容,注意这里是Client

highlighter- xml
<ItemGroup>
	<Protobuf Include="Protos\example.proto" GrpcServices="Client" />
</ItemGroup>

5.5 新建测试类

ExampleTest.cs

highlighter- csharp
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

highlighter- arduino
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调试即可

2078491-20230214194104483-192700589.png

7.源码地址

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

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK