欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-04-28 22:21:57
一. SignalR中DI思想的应用 DI,即依赖注入,它是一种不负责创建其自己的依赖项对象的一种模式,通常用来降低代码之间的耦合性,广泛应用于架构设计,是必不可少的一种思想。 下面结合一个需求来说一说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:

第六节:SignalR完结篇之依赖注入和分布式部署
  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类中进行配置。

代码如下图:

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

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

 代码如下:

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

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

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

5. 测试结果:

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

 

 

二. 基于SQLServer或Redis进行部署

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

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

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

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

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

 

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

 

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 端口下,进行通讯。

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

PS:补充Redis的配置

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

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

 

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

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 :
  • 声     明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。