一. 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:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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系列入門已經全部更新完成,再深入的需要小伙伴們自行研究了,原計劃的項目案例由於剝離代碼實在是太耗時間了,暫時擱置,後面有時間在補充,下一步會給該系列做一個目錄就徹底告一段落。
!
- 作 者 : Yaopengfei(姚鵬飛)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 聲 明1 : 本人才疏學淺,用郭德綱的話說“我是一個小學生”,如有錯誤,歡迎討論,請勿謾罵^_^。
- 聲 明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。