基于 .NET 8.0 gRPC通讯架构设计讲解,客户端+服务端

news2025/2/7 17:45:59

目录

1.简要说明

2.服务端设计

2.1 服务端创建

2.2 服务端设计

2.3 服务端业务模块

3.客户端设计-控制台

4.客户端设计-Avalonia桌面程序

5.客户端设计-MAUI安卓端程序


1.简要说明

gRPC 一开始由 google 开发,是一款语言中立、平台中立、开源的远程过程调用(RPC)系统

项目下载地址:https://download.csdn.net/download/rotion135/90342675

整体架构设计图

解决方案预览

2.服务端设计

2.1 服务端创建

新建项目,搜索grpc 选择下图中的项目,创建;

框架最低版本支持.NET 8.0

2.2 服务端设计

先说下几个重要的文件:

launchSettings.json  部署路径等先关信息的配置文件

appsettings.json   项目设置相关配置文件

手动添加:

      "Microsoft.AspNetCore.Hosting": "Information",
      "Microsoft.AspNetCore.Routing.EndpointMiddleware": "Information",

greet.proto  通讯交互的模型设计,以及命名空间

收发消息的接口定义等,添加完成后记得保存,重新生成一下

服务类定义,还有收发消息方法重写

寻找对应的服务以及调用的方法,用的反射的机制

参数传过来 ServerName 方法Method  以及入参 Args

从容器中寻找服务,获取到实例后,调用Method,最后返回结果

  public class GreeterService : Greeter.GreeterBase
  {
      private readonly ILogger<GreeterService> _logger;
      public GreeterService(ILogger<GreeterService> logger)
      {
          _logger = logger;
      }

      public override Task<MessageResult> SendMessage(LSRequest request, ServerCallContext context)
      {
          return GetResponse(request);
      }

      private async Task<MessageResult> GetResponse(LSRequest request)
      {
          return await Task.Run(() =>
          {
              ResponseModel response = new ResponseModel();
              try
              {
                  SendModel send = JsonConvert.DeserializeObject<SendModel>(request.Json);
                  if (send == null)
                  {
                      response.IsSuccess = false;
                      response.Message = "Request cannot be null";
                  }
                  else
                  {
                      if (string.IsNullOrEmpty(send.ServerName) || string.IsNullOrEmpty(send.Method))
                      {
                          response.IsSuccess = false;
                          response.Message = "ServerName or Method cannot be null";
                      }
                      else
                      {
                          response.RequestID = send.RequestID;

                          // 根据服务名称,寻找对应的服务
                          var service = BusinessModules.IocContainer.Get<IService>(send.ServerName);

                          if (service != null)
                          {
                              // 使用反射调用方法
                              MethodInfo methodInfo = null;

                              if (send.Args != null && send.Args.Length > 0)
                              {
                                  methodInfo = service.GetType().GetMethod(send.Method, send.Args.Select(arg => arg.GetType()).ToArray());
                              }
                              else
                              {
                                  methodInfo = service.GetType().GetMethod(send.Method, types: new List<Type>().ToArray());
                              }

                              if (methodInfo != null)
                              {
                                  try
                                  {
                                      var res = methodInfo.Invoke(service, send.Args); // 执行方法
                                      response.IsSuccess = true;
                                      response.Content = JsonConvert.SerializeObject(res);
                                  }
                                  catch (Exception ex)
                                  {
                                      LogOperate.Error("Method invocation failed", ex);
                                      response.IsSuccess = false;
                                      response.Message = ex.Message;
                                  }
                              }
                              else
                              {
                                  response.IsSuccess = false;
                                  response.Message = "Method not found";
                              }
                          }
                          else
                          {
                              response.IsSuccess = false;
                              response.Message = "Server not found";
                          }
                      }
                  }
              }
              catch (Exception ex)
              {
                  LogOperate.Error("GetResponse 发生异常", ex);
                  response.IsSuccess = false;
                  response.Message = ex.Message;
              }
              return new MessageResult()
              {
                  Json = JsonConvert.SerializeObject(response)
              };
          });
      }


  }

在程序运行时,将服务注入

一些业务相关的服务启动,用的BusinessModules来管理,下面会介绍。

2.3 服务端业务模块

业务模块,用了我自己写的IOC容器来管理

定义服务,继承IService

业务模块启动:

     /// <summary>
     /// 模块启动
     /// </summary>
     public static void OnStart()
     {
         try 
         {
             IEnumerable<Type> types = GetService();
             if (types != null && types.Count() > 0)
             {
                 foreach (Type type in types)
                 {
                     string serviceName = type.Name;
                     // 获取 ServiceNameAttribute 特性
                     var attribute = type.GetCustomAttribute<ServiceNameAttribute>();
                     if (attribute != null) 
                     {
                         serviceName = attribute.Name;
                     }

                     //注册平台 基于IService的服务平台
                     _iocBuilder.RegisterType(type,serviceName,LifeTimeType.Singleton);
                 }
             }

             //创建容器
             IocContainer = _iocBuilder.Build();

             //获取所有注册的服务,基于IService的实现
             //然后调用服务的启动方法
             var services = IocContainer.GetAllService();
             foreach (var service in services)
             {
                 try
                 {
                     service.OnStart();
                 }
                 catch (Exception ex)
                 {
                     LogOperate.Start($"启动容器中的服务发生异常,\r\n" + ex.ToString());
                 }
             }


         }
         catch(Exception  ex)
         {
             LogOperate.Error("BusinessModules-OnStart", ex);
         }
     }

业务模块结束:

     /// <summary>
     /// 模块结束
     /// </summary>
     public static void OnStop() 
     {
         try
         {
             var services = IocContainer.GetAllService();
             foreach (var service in services)
             {
                 try
                 {
                     service.OnStop();
                 }
                 catch (Exception ex)
                 {
                     LogOperate.Start($"停止容器中的服务发生异常,\r\n" + ex.ToString());
                 }
             }
         }
         catch (Exception ex)
         {
             LogOperate.Error("BusinessModules-OnStop", ex);
         }
     }

3.客户端设计-控制台

客户端设计可以多种方式,项目初始化时,需要Nuget引用下面三个包:

Google.Protobuf

Grpc.Net.Client

Grpc.Tools

添加 Protos 文件夹,添加文件greet.proto

除了命名空间修改为当前项目的之外,其余的与服务中的文件一致

运行时,创建链接,调用服务中的方法

using var channle = GrpcChannel.ForAddress("http://127.0.0.1:5237");

var client = new Greeter.GreeterClient(channle);
SendModel send = new SendModel();
send.RequestID = Guid.NewGuid().ToString();
send.ServerName = "Device";
send.Method = "GetDeviceInfo";
//send.Args = new object[1];
//send.Args[0] = "1231";
var replay = await client.SendMessageAsync(new LSRequest() { Json = JsonConvert.SerializeObject(send) });
Console.WriteLine("Response:" + replay.Json);
Console.ReadKey();

4.客户端设计-Avalonia桌面程序

Avalonia 创建项目

同样需要添加三个包

同样需要添加 Protos 文件夹,添加文件greet.proto

除了命名空间修改为当前项目的之外,其余的与服务中的文件一致

客户端的设计我在这里就不多说了,可以看我的其他文章,或者下载源码来查看

这里边封装了一个gRPC的客户端类

    public class GRPC_Control
    {
        private string url;
        private Greeter.GreeterClient client;
        private GrpcChannel channle;
        public GRPC_Control(string _url)
        {
            url = _url;
        }

        public BaseResult Connect()
        {
            //"http://localhost:5237"
            channle = GrpcChannel.ForAddress(url);

            client = new Greeter.GreeterClient(channle);

            return BaseResult.Successed;
        }



        public BaseResult SendMessage(string service, string method, object[] args = null)
        {
            try
            {
                SendModel send = new SendModel();
                send.RequestID = Guid.NewGuid().ToString();
                send.ServerName = service;
                send.Method = method;
                send.Args = args;
                var replay = client.SendMessage(new LSRequest() { Json = JsonConvert.SerializeObject(send) });
                ResponseModel response = JsonConvert.DeserializeObject<ResponseModel>(replay.Json);
                if (response.IsSuccess)
                {
                    return JsonConvert.DeserializeObject<BaseResult>(response.Content);
                }
                else
                {
                    return new BaseResult(false, response.Message);
                }
            }
            catch (Exception ex)
            {
                LogOperate.Error("SendMessage", ex);
                return new BaseResult(false, ex.Message);
            }
        }

        public async Task<BaseResult> SendMessageAsync(string service, string method, object[] args = null)
        {
            return await Task.Run(async () =>
            {
                try
                {
                    SendModel send = new SendModel();
                    send.RequestID = Guid.NewGuid().ToString();
                    send.ServerName = service;
                    send.Method = method;
                    send.Args = args;
                    var replay = await client.SendMessageAsync(new LSRequest() { Json = JsonConvert.SerializeObject(send) });
                    ResponseModel response = JsonConvert.DeserializeObject<ResponseModel>(replay.Json);
                    if (response.IsSuccess)
                    {
                        return JsonConvert.DeserializeObject<BaseResult>(response.Content);
                    }
                    else
                    {
                        return new BaseResult(false, response.Message);
                    }
                }
                catch (Exception ex)
                {
                    LogOperate.Error("SendMessageAsync", ex);
                    return new BaseResult(false, ex.Message);
                }
            });
        }

    }

调用方式:

 case "Connect":
     if (gRPC == null)
     {
         gRPC = new GRPC_Control(Url);
         gRPC.Connect();
     }
     VM_MainWindow.Instance.Popup("连接成功");
     break;
 case "Send":
     if (gRPC != null)
     {
         var res= gRPC.SendMessage(Service, Method);
         Respone=JsonConvert.SerializeObject(res);
     }
     break;

5.客户端设计-MAUI安卓端程序

创建MAUI 项目,MAUI具体的使用可以参考官方文档,这里边就不展开说了

同样也是需要引用三个包

同样需要添加 Protos 文件夹,添加文件greet.proto

除了命名空间修改为当前项目的之外,其余的与服务中的文件一致

封装的gRPC客户端类与Avalonia的一致

如果对此架构感兴趣,欢迎下载源码参考参考,如有更好的建议,欢迎评论区提出

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2294403.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java实习生面试题汇总

Java实习生面试题汇总 简介 本人是二本大三学生&#xff0c;下半年大四。暑假在上海这边找实习工作&#xff0c;面了几家公司&#xff0c;所问到的问题记录在下面。 因为是在校生&#xff0c;没任何实习经历&#xff0c;一般找我面试的都是小公司&#xff0c;一般问的比较简…

Java 如何覆盖第三方 jar 包中的类

目录 一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理 背景&#xff1a; 在我们日常的开发中&#xff0c;经常需要使用第三方的 jar 包&#xff0c;有时候我们会发现第三方的 jar 包中的某一个类有问题&#xff0c;或者我们需要定制化修改其中的逻辑&#xff0c…

深度学习系列--04.梯度下降以及其他优化器

目录 一.梯度概念 1.一元函数 2.二元函数 3.几何意义上的区别 二.梯度下降 1.原理 2.步骤 3.示例代码&#xff08;Python&#xff09; 4.不同类型的梯度下降 5.优缺点 三.动量优化器&#xff08;Momentum&#xff09; 适用场景 1.复杂地形的优化问题 2.数据具有噪声的问…

【C语言篇】“三子棋”

一、游戏介绍 三子棋&#xff0c;英文名为 Tic - Tac - Toe&#xff0c;是一款简单而经典的棋类游戏。游戏在一个 33 的棋盘上进行&#xff0c;两名玩家轮流在棋盘的空位上放置自己的棋子&#xff08;通常用 * 和 # 表示&#xff09;&#xff0c;率先在横、竖或斜方向上连成三个…

TongSearch3.0.4.0安装和使用指引(by lqw)

文章目录 安装准备手册说明支持的数据类型安装控制台安装单节点(如需集群请跳过这一节)解压和启动开启X-Pack Security和生成p12证书&#xff08;之后配置内置密码和ssl要用到&#xff09;配置内置用户密码配置ssl&#xff08;先配置内置用户密码再配ssl&#xff09;配置控制台…

在本地顺利的部署一个al模型从零开始 windows

引言 &#xff08;踩的坑&#xff0c;省流引言的内容没有有使模型跑起来&#xff09; 最近想在本地部署一个deepseek模型&#xff0c;就在网上搞了3 4天终于是能够部署下来了&#xff0c;在部署的时候也是成功的踩了无数的坑&#xff0c;比如我先问al如何在本地部署一个语言模…

【容器技术01】使用 busybox 构建 Mini Linux FS

使用 busybox 构建 Mini Linux FS 构建目标 在 Linux 文件系统下构建一个 Mini 的文件系统&#xff0c;构建目标如下&#xff1a; minilinux ├── bin │ ├── ls │ ├── top │ ├── ps │ ├── sh │ └── … ├── dev ├── etc │ ├── g…

尝试在Excel里调用硅基流动上的免费大语言模型

我个人觉得通过api而不是直接浏览器客户端聊天调用大语言模型是使用人工智能大模型的一个相对进阶的阶段。 于是就尝试了一下。我用的是老师木 袁进辉博士新创的硅基流动云上的免费的大模型。——虽然自己获赠了不少免费token&#xff0c;但测试阶段用不上。 具体步骤如下&am…

SqlSugar简单使用之Nuget包封装-Easy.SqlSugar.Core

SqlSugar官方文档 Nuget包开源地址 Nuget包是为了简化SqlSugar的使用步骤,增加一些基础的使用封装 引入分为两个版本&#xff0c;一个Ioc模式&#xff0c;另一个是注入模式&#xff0c;如果不想影响原本的仓储代码推荐使用Ioc模式&#xff0c;两者区别不到&#xff0c;方法通…

Linux网络 | 理解NATPT, 数据链路层Done

前言&#xff1a;本节内容结束数据链路层&#xff0c; 本节的重要内容有两个&#xff1a;一个是见一个综合性面试题&#xff0c;另一个就是NAT技术NATPT。 那么废话不多说&#xff0c; 开始我们的学习吧&#xff01;&#xff01;&#xff01; ps&#xff1a;最好先看一下上一篇…

微信小程序~django Petting pets(爱抚宠物)小程序

博主介绍&#xff1a;✌程序猿徐师兄、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

UE (标识符, meta=())笔记

视频连接&#xff1a; [UFSH2024]UE5(标识符, meta(详解, 史上最全)) | 大钊 Epic Games 虚幻社区经理 参考文档&#xff1a; UE5标识符详解 | 史上最全 UnrealSpecifiers | UE5标识符详解 GitHub 老外整理的标识符文档 标识符 CPP_Default_ParamName&#xff08;18:22&a…

并发编程 引用类型 原子类 Stamped和Markable atomicMarkableReference表单Ref和标记Markable 面试题

目录 Stamped 和 Markable 的区别 示例代码 所以这个东西是一次性的 从来没听说过 从来没见过 Stamped 和 Markable 的区别 标记号 boolean 一次性的 版本号 整型数 不建议用 Markable 解决 ABA 问题 AtomicMarkableReference 是一个位于 java.util.concurrent.atomic 包中…

绿联NAS安装cpolar内网穿透工具实现无公网IP远程访问教程

文章目录 前言1. 开启ssh服务2. ssh连接3. 安装cpolar内网穿透4. 配置绿联NAS公网地址 前言 本文主要介绍如何在绿联NAS中使用ssh远程连接后&#xff0c;使用一行代码快速安装cpolar内网穿透工具&#xff0c;轻松实现随时随地远程访问本地内网中的绿联NAS&#xff0c;无需公网…

【Ubuntu】ARM交叉编译开发环境解决“没有那个文件或目录”问题

【Ubuntu】ARM交叉编译开发环境解决“没有那个文件或目录”问题 零、起因 最近在使用Ubuntu虚拟机编译ARM程序&#xff0c;解压ARM的GCC后想要启动&#xff0c;报“没有那个文件或目录”&#xff0c;但是文件确实存在&#xff0c;环境配置也检查过了没问题&#xff0c;本文记…

微信小程序~电器维修系统小程序

博主介绍&#xff1a;✌程序猿徐师兄、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

android 适配 api 35(android 15) 遇到的问题

首先升级 targetSdkVersion 和 compileSdkVersion 到 35&#xff0c;升级后发生的报错 一、 解决方案: 升级 gradle 和 gradle 插件版本 com.android.tools.build:gradle -> 8.3.0-alpha02 gradle-wrapper.properties : distributionUrl -> gradle-8.6-bin.zip htt…

Python Pandas(3):DataFrame

1 介绍 DataFrame 是 Pandas 中的另一个核心数据结构&#xff0c;类似于一个二维的表格或数据库中的数据表。它含有一组有序的列&#xff0c;每列可以是不同的值类型&#xff08;数值、字符串、布尔型值&#xff09;。DataFrame 既有行索引也有列索引&#xff0c;它可以被看做由…

Mac电脑上好用的压缩软件

在Mac电脑上&#xff0c;有许多优秀的压缩软件可供选择&#xff0c;这些软件不仅支持多种压缩格式&#xff0c;还提供了便捷的操作体验和强大的功能。以下是几款被广泛推荐的压缩软件&#xff1a; BetterZip 功能特点&#xff1a;BetterZip 是一款功能强大的压缩和解压缩工具&a…

BUUCTF_XSS-Lab

xss XSS&#xff08;Cross - Site Scripting&#xff09;即跨站脚本攻击&#xff0c;是一种常见的 Web 安全漏洞。攻击者通过在目标网站注入恶意脚本&#xff08;通常是 JavaScript&#xff09;&#xff0c;当其他用户访问该网站时&#xff0c;这些恶意脚本会在用户的浏览器中执…