• 第五节:SignalR完结篇之依赖注入和分布式部署


    一. SignalR中DI思想的应用

      DI,即依赖注入,它是一种不负责创建其自己的依赖项对象的一种模式,通常用来降低代码之间的耦合性,广泛应用于架构设计,是必不可少的一种思想。
      下面结合一个需求来说一说SignalR中依赖注入思想的应用。
      需求:比如在前面章节的聊天室案例中,想把发送的每条消息都记录下来 (下面的代码中,使用群发这个接口进行测试)。

     分析解决思路:

     1. 新建Repository类和IRepository接口,里面声明SaveMsg方法,用来存储信息 (PS:便于测试,这里将信息保存到txt文本文档中)

    代码如下:

    复制代码
     1   public interface IRepository
     2   {
     3         void SaveMsg(string connectionId, string msg);
     4   }
     5   public class Repository : IRepository
     6   {
     7         /// <summary>
     8         /// 模拟数据库插入操作
     9         /// 这里以日志代替
    10         /// </summary>
    11         /// <param name="connectionId"></param>
    12         /// <param name="msg"></param>
    13         public void SaveMsg(string connectionId, string msg)
    14         {
    15             //此处执行插入数据库操作
    16             FileOperateHelp.WriteFile("/Logs/msg.txt", $"用户【{connectionId}】发来消息:{msg},时间为:{DateTime.Now.ToLongDateString()}");
    17         }
    18   }
    复制代码

    分享一个文件相关操作的工具类FileOperateHelp:

      1  public class FileOperateHelp
      2     {
      3         #region 01.写文件(.txt-覆盖)
      4         /// <summary>
      5         /// 写文件(覆盖源文件内容)
      6         /// 文件不存在的话自动创建
      7         /// </summary>
      8         /// <param name="FileName">文件路径(web里相对路径,控制台在根目录下写)</param>
      9         /// <param name="Content">文件内容</param>
     10         public static string Write_Txt(string FileName, string Content)
     11         {
     12             try
     13             {
     14                 Encoding code = Encoding.GetEncoding("gb2312");
     15                 string htmlfilename = FileOperateHelp.PathConvert(FileName);
     16                 //string htmlfilename = HttpContext.Current.Server.MapPath(FileName + ".txt"); //保存文件的路径  
     17                 string str = Content;
     18                 StreamWriter sw = null;
     19                 {
     20                     try
     21                     {
     22                         sw = new StreamWriter(htmlfilename, false, code);
     23                         sw.Write(str);
     24                         sw.Flush();
     25                     }
     26                     catch { }
     27                 }
     28                 sw.Close();
     29                 sw.Dispose();
     30                 return "ok";
     31             }
     32             catch (Exception ex)
     33             {
     34 
     35                 return ex.Message;
     36             }
     37 
     38         }
     39         #endregion
     40 
     41         #region 02.读文件(.txt)
     42         /// <summary>
     43         /// 读文件
     44         /// </summary>
     45         /// <param name="filename">文件路径(web里相对路径,控制台在根目录下写)</param>
     46         /// <returns></returns>
     47         public static string Read_Txt(string filename)
     48         {
     49 
     50             try
     51             {
     52                 Encoding code = Encoding.GetEncoding("gb2312");
     53                 string temp = FileOperateHelp.PathConvert(filename);
     54                 //  string temp = HttpContext.Current.Server.MapPath(filename + ".txt");
     55                 string str = "";
     56                 if (File.Exists(temp))
     57                 {
     58                     StreamReader sr = null;
     59                     try
     60                     {
     61                         sr = new StreamReader(temp, code);
     62                         str = sr.ReadToEnd(); // 读取文件  
     63                     }
     64                     catch { }
     65                     sr.Close();
     66                     sr.Dispose();
     67                 }
     68                 else
     69                 {
     70                     str = "";
     71                 }
     72                 return str;
     73             }
     74             catch (Exception ex)
     75             {
     76 
     77                 return ex.Message;
     78             }
     79         }
     80         #endregion
     81 
     82         #region 03.写文件(.txt-添加)
     83         /// <summary>  
     84         /// 写文件  
     85         /// </summary>  
     86         /// <param name="FileName">文件路径(web里相对路径,控制台在根目录下写)</param>  
     87         /// <param name="Strings">文件内容</param>  
     88         public static string WriteFile(string FileName, string Strings)
     89         {
     90             try
     91             {
     92                 string Path = FileOperateHelp.PathConvert(FileName);
     93 
     94                 if (!System.IO.File.Exists(Path))
     95                 {
     96                     System.IO.FileStream f = System.IO.File.Create(Path);
     97                     f.Close();
     98                     f.Dispose();
     99                 }
    100                 System.IO.StreamWriter f2 = new System.IO.StreamWriter(Path, true, System.Text.Encoding.UTF8);
    101                 f2.WriteLine(Strings);
    102                 f2.Close();
    103                 f2.Dispose();
    104                 return "ok";
    105             }
    106             catch (Exception ex)
    107             {
    108 
    109                 return ex.Message;
    110             }
    111         }
    112         #endregion
    113 
    114         #region 04.读文件(.txt)
    115         /// <summary>  
    116         /// 读文件  
    117         /// </summary>  
    118         /// <param name="FileName">文件路径(web里相对路径,控制台在根目录下写)</param>  
    119         /// <returns></returns>  
    120         public static string ReadFile(string FileName)
    121         {
    122             try
    123             {
    124                 string Path = FileOperateHelp.PathConvert(FileName);
    125                 string s = "";
    126                 if (!System.IO.File.Exists(Path))
    127                     s = "不存在相应的目录";
    128                 else
    129                 {
    130                     StreamReader f2 = new StreamReader(Path, System.Text.Encoding.GetEncoding("gb2312"));
    131                     s = f2.ReadToEnd();
    132                     f2.Close();
    133                     f2.Dispose();
    134                 }
    135                 return s;
    136             }
    137             catch (Exception ex)
    138             {
    139                 return ex.Message;
    140             }
    141         }
    142         #endregion
    143 
    144         #region 05.删除文件
    145         /// <summary>  
    146         /// 删除文件  
    147         /// </summary>  
    148         /// <param name="Path">文件路径(web里相对路径,控制台在根目录下写)</param>  
    149         public static string FileDel(string Path)
    150         {
    151             try
    152             {
    153                 string temp = FileOperateHelp.PathConvert(Path);
    154                 File.Delete(temp);
    155                 return "ok";
    156             }
    157             catch (Exception ex)
    158             {
    159                 return ex.Message;
    160             }
    161         }
    162         #endregion
    163 
    164         #region 06.移动文件
    165         /// <summary>  
    166         /// 移动文件  
    167         /// </summary>  
    168         /// <param name="OrignFile">原始路径(web里相对路径,控制台在根目录下写)</param>  
    169         /// <param name="NewFile">新路径,需要写上路径下的文件名,不能单写路径(web里相对路径,控制台在根目录下写)</param>  
    170         public static string FileMove(string OrignFile, string NewFile)
    171         {
    172             try
    173             {
    174                 OrignFile = FileOperateHelp.PathConvert(OrignFile);
    175                 NewFile = FileOperateHelp.PathConvert(NewFile);
    176                 File.Move(OrignFile, NewFile);
    177                 return "ok";
    178             }
    179             catch (Exception ex)
    180             {
    181                 return ex.Message;
    182             }
    183         }
    184         #endregion
    185 
    186         #region 07.复制文件
    187         /// <summary>  
    188         /// 复制文件  
    189         /// </summary>  
    190         /// <param name="OrignFile">原始文件(web里相对路径,控制台在根目录下写)</param>  
    191         /// <param name="NewFile">新文件路径(web里相对路径,控制台在根目录下写)</param>  
    192         public static string FileCopy(string OrignFile, string NewFile)
    193         {
    194             try
    195             {
    196                 OrignFile = FileOperateHelp.PathConvert(OrignFile);
    197                 NewFile = FileOperateHelp.PathConvert(NewFile);
    198                 File.Copy(OrignFile, NewFile, true);
    199                 return "ok";
    200             }
    201             catch (Exception ex)
    202             {
    203                 return ex.Message;
    204             }
    205         }
    206         #endregion
    207 
    208         #region 08.创建文件夹
    209         /// <summary>  
    210         /// 创建文件夹  
    211         /// </summary>  
    212         /// <param name="Path">相对路径(web里相对路径,控制台在根目录下写)</param>  
    213         public static string FolderCreate(string Path)
    214         {
    215             try
    216             {
    217                 Path = FileOperateHelp.PathConvert(Path);
    218                 // 判断目标目录是否存在如果不存在则新建之  
    219                 if (!Directory.Exists(Path))
    220                 {
    221                     Directory.CreateDirectory(Path);
    222                 }
    223                 return "ok";
    224             }
    225             catch (Exception ex)
    226             {
    227                 return ex.Message;
    228             }
    229         }
    230         #endregion
    231 
    232         #region 09.递归删除文件夹目录及文件
    233         /// <summary>  
    234         /// 递归删除文件夹目录及文件  
    235         /// </summary>  
    236         /// <param name="dir">相对路径(web里相对路径,控制台在根目录下写) 截止到哪删除到哪,eg:/a/ 连a也删除</param>    
    237         /// <returns></returns>  
    238         public static string DeleteFolder(string dir)
    239         {
    240 
    241             try
    242             {
    243                 string adir = FileOperateHelp.PathConvert(dir);
    244                 if (Directory.Exists(adir)) //如果存在这个文件夹删除之   
    245                 {
    246                     foreach (string d in Directory.GetFileSystemEntries(adir))
    247                     {
    248                         if (File.Exists(d))
    249                             File.Delete(d); //直接删除其中的文件                          
    250                         else
    251                             DeleteFolder(d); //递归删除子文件夹   
    252                     }
    253                     Directory.Delete(adir, true); //删除已空文件夹                   
    254                 }
    255                 return "ok";
    256             }
    257             catch (Exception ex)
    258             {
    259                 return ex.Message;
    260             }
    261         }
    262 
    263         #endregion
    264 
    265         #region 10.将相对路径转换成绝对路径
    266         /// <summary>
    267         /// 10.将相对路径转换成绝对路径
    268         /// </summary>
    269         /// <param name="strPath">相对路径</param>
    270         public static string PathConvert(string strPath)
    271         {
    272             //web程序使用
    273             if (HttpContext.Current != null)
    274             {
    275                 return HttpContext.Current.Server.MapPath(strPath);
    276             }
    277             else //非web程序引用             
    278             {
    279                 strPath = strPath.Replace("/", "\");
    280                 if (strPath.StartsWith("\"))
    281                 {
    282                     strPath = strPath.TrimStart('\');
    283                 }
    284                 return System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, strPath);
    285             }
    286         }
    287         #endregion
    288 
    289     }
    View Code

    2. 采用构造函数注入的方式,在MySpecHub1这个Hub类中进行配置。

    代码如下图:

    3. 配置注入代码,在Startup类中的Configuration方法中,进行依赖注入代码的配置。

     代码如下:

      每当需要创建MySpecHub1实例,SignalR 将调用此匿名函数。
      GlobalHost.DependencyResolver.Register(typeof(MySpecHub1), () => new MySpecHub1(new Repository()));

     4. 在群发接口中进行SaveMsg方法的调用进行测试。

    5. 测试结果:

    二. 基于SQLServer或Redis进行部署

      我们都知道,当用户量并发量非常大的时候,单台服务器已经无法承载所需的业务,这个时候我们会配置负载均衡,项目会部署在多台服务器上,通常利用Nginx进行反向代理。
      另外在使用SignalR的过程中,你会发现,当连接数比较大的时候,会比较卡顿,所以分布式部署或许是一种不错的解决方案,但我们会面临一个问题,如何打通不同地址间的SignalR的通讯呢?
      这个时候可以引入“中间件”的概念,比如可以用“SQLServer”或“Redis”为底板,来实现不同地址间SignalR的通讯。(此方案可能非最佳方案,不喜勿喷)
    PS:
      1.  引入“中间件”后,SignalR之间的通讯势必会减慢,正如鱼和熊掌不可兼得哦。
      2.  以Redis为底板性能肯定要比SQLServer要高的多。
      

     下面以SQLServer为例简单的配置一下。

    1. 通过Nuget下载程序集:Microsoft.AspNet.SignalR.SqlServer

    2. 在SQLServer中新建一个数据库,比如 SignalRDB,不需要创建任何表,因为程序运行时,会自动生成所需表

    3. 在Startup中配置映射数据库,代码如下:

    复制代码
     1   public class Startup
     2     {
     3         public void Configuration(IAppBuilder app)
     4         {   
     5             app.UseCors(CorsOptions.AllowAll).MapSignalR();
     6             //四. 性能优化 
     7            // 1. SQLServer版本(跨服务器通信代码配置)
     8             string sqlConnectionString = "data source=localhost;initial catalog=SignalRDB;persist security info=True;user id=sa;password=123456;";
     9             GlobalHost.DependencyResolver.UseSqlServer(sqlConnectionString);
    10    
    11         }
    12     }
    复制代码

     以上3步,已经实现了不同地址间SignalR间的通讯,配置非常简单,内部复杂实现微软已经给实现好了,那么下面我们简单的部署一下,分别部署在1001 和 1002 端口下,进行通讯。

     

    PS:补充Redis的配置

    1. 通过Nuget下载程序集:Microsoft.AspNet.SignalR.Redis

    2. 代码配置:GlobalHost.DependencyResolver.UseRedis("127.0.0.1", 6379, "123456", "mykey");

    截止到此处Signalr系列入门已经全部更新完成,再深入的需要小伙伴们自行研究了,原计划的项目案例由于剥离代码实在是太耗时间了,暂时搁置,后面有时间在补充,下一步会给该系列做一个目录就彻底告一段落。

    *********转摘:https://www.cnblogs.com/yaopengfei/p/9353630.html

  • 相关阅读:
    封装transform函数(设置和获取transform的属性和属性值)
    layui第三方组件 inputTags 标签输入框
    layui编辑器(layedit)的实现和图片上传功能
    php编写抽奖后台实现抽奖概率计算
    laravel中使用事物
    laravel 使用jwt的基本应用(适于初始jwt)
    layui 下拉框动态添加数据(监听下拉框(select)事件)
    laravel后台账户登录验证(5.5.48版本)
    使用三目运算获取3个数值中最大的数值
    Laravel框架使用融云服务端SDK
  • 原文地址:https://www.cnblogs.com/linybo/p/13925886.html
Copyright © 2020-2023  润新知