• .Net Core(.Net6)创建grpc


    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? 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
        {
            /// 
            /// 颁发JWT Token
            /// 
            /// 
            /// 
            /// 
            public static string GenerateJwt(string securityKey, string accountName)
            {
                var claims = new List
                {
                    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;
            }
    
            /// 
            /// 解析
            /// 
            /// 
            /// 
            /// 
            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 _logger;
    
            public ExampleService(ILogger logger)
            {
                _logger = logger;
            }
    
            public override Task 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(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(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 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 StreamingFromClient(IAsyncStreamReader 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 requestStream, IServerStreamWriter 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();
            }
    
            /// 
            /// 创建客户端链接
            /// 
            /// 
            /// 
            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(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文件夹分别拷贝到GrpcServer 根目录GrpcClient\bin\Debug\net6.0

    6.2 启动程序

    先运行GrpcServer在运行GrpcClient即可

    6.3 调试

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

    7.源码地址

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


    __EOF__

  • 本文作者: Dennis
  • 本文链接: https://www.cnblogs.com/dennisdong/p/17120990.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    数据智能类API推荐
    1036 Boys vs Girls
    Unity ML-Agents默认接口参数含义
    vsto excel 表格存在循环引用 删除名称管理器很慢
    springboot整合mybatis(配置模式+注解模式)
    为什么页面跟设计稿差距这么大 优漫动游
    位图的详细介绍及模拟实现
    Java虚拟机整型数加载指令学习
    如何设计一个“好的“测试用例?
    LeetCode刷题---19. 删除链表的倒数第 N 个结点(双指针-快慢指针)
  • 原文地址:https://www.cnblogs.com/dennisdong/p/17120990.html