第五節:SignalR完結篇之依賴注入和分散式部署
一. SignalR中DI思想的應用
DI,即依賴注入,它是一種不負責建立其自己的依賴項物件的一種模式,通常用來降低程式碼之間的耦合性,廣泛應用於架構設計,是必不可少的一種思想。 下面結合一個需求來說一說SignalR中依賴注入思想的應用。 需求:比如在前面章節的聊天室案例中,想把傳送的每條訊息都記錄下來 (下面的程式碼中,使用群發這個介面進行測試)。分析解決思路:
1. 新建Repository類和IRepository介面,裡面宣告SaveMsg方法,用來儲存資訊 (PS:便於測試,這裡將資訊儲存到txt文字文件中)
程式碼如下:
1 public interfaceIRepository 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