一. 承上聲明 在上一個章節里,啰里啰嗦寫了一堆關於介紹SignalR的“廢話”,從這一篇開始往後正式擼代碼,這期間不少人(包括 張善友大哥)建議我直接用.Net Core下的SignalR,關於此簡單說一下,雖然我們要跟上時代步伐,但目前絕多數.Net項目都是基於 .Net FrameWork下的 ...
一. 承上聲明
在上一個章節里,啰里啰嗦寫了一堆關於介紹SignalR的“廢話”,從這一篇開始往後正式擼代碼,這期間不少人(包括 張善友大哥)建議我直接用.Net Core下的SignalR,關於此簡單說一下,雖然我們要跟上時代步伐,但目前絕多數.Net項目都是基於 .Net FrameWork下的而非 .Net Core, 並且做事要有始有終,既然打算寫這個系列,就不能半途而廢,這個.Net FrameWork下的SignalR系列務必要寫完。 還有一點,不怕笑話,.Net Core雖然我也有研究,但並沒有多麼深入,暫時就不出來獻醜了,後面等熟悉了,再來補充.Net Core下的SignalR的用法。 這一節的主要內容: PersistentConnection模型 從零開始搭建的步驟、Web瀏覽器端和C#伺服器端核心方法的使用介紹、分組的概念、開啟跨域的兩種方式。 這一節的不足:沒有體現SignalR的生命周期、沒有斷線重連的合理處理、沒有心跳檢測。幾點介紹:
1. PersistentConnection(永久連接)相對於Hubs模式,更加偏向底層,它的編程模式與WebSocket的寫法很類似,固定方法發送和接受,不能向Hub模式那樣 客戶端和服務端相互調用各自定義的方法。
2. 該模型主要用於:單個發件人、分組、廣播消息的簡單終結點。
二. 從零開始搭建
1. 新建MVC5項目,通過Nuget安裝:Microsoft.AspNet.SignalR程式集,安裝成功後如下圖:
2. 新建一個永久連接模型類(MyPresitentConnection1),該類繼承了PersistentConnection,並且override幾個必要方法。
3. 新建一個OWIN Startup Class(Startup),併在Configuration方法中指定使用的通訊模型的URl, 如: app.MapSignalR<MyPresitentConnection1>("/myPreConnection1");
PS: 程式啟動時候首先會找到該類,然後運行裡面的Configuration方法,從而url和通訊模型的匹配將生效。
4. 在前端頁面中書寫SignalR的代碼,與伺服器端MyPresitentConnection1類進行連接,實現相應的通訊業務。
三. 核心方法介紹
1. 伺服器端代碼
(1). OWIN Startup Class即Startup中要配置url和通訊模型向匹配,這裡的url在web前端頁面的js中要使用,代碼如下:
1 public class Startup 2 { 3 public void Configuration(IAppBuilder app) 4 { 5 // 有關如何配置應用程式的詳細信息,請訪問 https://go.microsoft.com/fwlink/?LinkID=316888 6 //1. 基本用法的配置 7 app.MapSignalR<MyPresitentConnection1>("/myPreConnection1"); 8 } 9 }
(2). 永久連接模型類MyPresitentConnection1繼承了PersistentConnection,並且可以override幾個方法。
A. PersistentConnection中可以override的幾個主要方法有:
①. OnConnected :連接成功後調用
②. OnReceived:接收到請求的時候調用
③. OnDisconnected:連接中斷的時候調用
④. OnReconnected:連接超時重新連接的時候調用
B. 核心業務主要使用PersistentConnection類中的Connection屬性,有兩個核心方法
①. 1對1發送消息: public static Task Send(string connectionId, object value);
②. 1對多發送消息: public static Task Send(IList<string> connectionIds, object value);
③. 廣播(群發,可以去掉不發送的人): public static Task Broadcast(object value, params string[] excludeConnectionIds);
PS:發現每個override里都有一個參數connectionId,它代表,每個客戶端連接伺服器成功後都會產生一個標記,這個標記是GUID產生的,它是唯一的, 不會重覆, 在業務中可以通過該標記connectionId來區分客戶端。
下麵我的代碼中書寫的業務為:
①. OnConnected方法即連接成功後調用的方法,調用Send方法告訴自己登錄成功(當然你也可以根據實際業務告訴指定的人)。
②. OnReceived方法即接受請求的方法,調用Send方法向指定人一對一發送消息。
③. OnDisconnected方法即連接中斷的方法,調用Broadcast方法向所有人發送消息,某某已經退出。
④. OnReconnected方法即超時重新連接方法,執行重連業務。
分享代碼:

1 public class TempData 2 { 3 /// <summary> 4 /// 接收人的connectionId 5 /// </summary> 6 public string receiveId { get; set; } 7 8 /// <summary> 9 /// 發送內容 10 /// </summary> 11 public string msg { get; set; } 12 }View Code

1 public class MyPresitentConnection1 : PersistentConnection 2 { 3 //下麵的兩個方法OnConnected 和 OnReceived預設帶的 4 5 /// <summary> 6 /// 連接成功後的方法 7 /// </summary> 8 /// <param name="request"></param> 9 /// <param name="connectionId"></param> 10 /// <returns></returns> 11 protected override Task OnConnected(IRequest request, string connectionId) 12 { 13 //Send方法,向指定人發送消息 14 return Connection.Send(connectionId, $"用戶:{connectionId}登錄成功"); 15 } 16 17 /// <summary> 18 /// 接收請求的方法 19 /// </summary> 20 /// <param name="request"></param> 21 /// <param name="connectionId"></param> 22 /// <param name="data"></param> 23 /// <returns></returns> 24 protected override Task OnReceived(IRequest request, string connectionId, string data) 25 { 26 //一對一發送消息 27 //data是一個json對象 { receiveId: $("#j_receiveId").val(), msg: $("#j_content").val() } 28 var model = JsonConvert.DeserializeObject<TempData>(data); 29 30 return Connection.Send(model.receiveId, model.msg); 31 } 32 33 /// <summary> 34 /// 連接中斷調用方法 35 /// </summary> 36 /// <param name="request"></param> 37 /// <param name="connectionId"></param> 38 /// <param name="stopCalled"></param> 39 /// <returns></returns> 40 protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled) 41 { 42 //告訴所有人該用戶退出了(包括自己,也可以配置排除一些用戶) 43 Connection.Broadcast( $"有用戶{connectionId}已經退出"); 44 return base.OnDisconnected(request, connectionId, stopCalled); 45 } 46 47 /// <summary> 48 /// 當連接在超時後重新連接時調用該方法 49 /// </summary> 50 /// <param name="request"></param> 51 /// <param name="connectionId"></param> 52 /// <returns></returns> 53 protected override Task OnReconnected(IRequest request, string connectionId) 54 { 55 return base.OnReconnected(request, connectionId); 56 } 57 }View Code
2. 前端Html頁面
(1). 引入JS庫,這裡包括JQuery庫和SignalR庫(JQuery最低版本為1.6.4)。
(2). 配置路徑:$.connection("/myPreConnection1");需要與Startup中的對應
(3). 常用的幾個方法有:
① start:開啟連接
② received:接受伺服器發送來的消息
③ disconnected:連接中斷時調用
④ error:連接發生錯誤的時嗲用
④ stop:斷開連接
⑤ send:發送消息
另外還有:connectionSlow、stateChanged、reconnecting、reconnected等等
(4). 當前連接狀態有4種
connecting: 0(正在連接), connected: 1(正常連接,連接成功中), reconnecting: 2(正在重連), disconnected: 4 (掉線了)
PS: 以上代碼和WebSocket確實很像,下圖為WebSocket相關方法。
(5). 下麵我的代碼中的業務
分享代碼:

1 @{ 2 Layout = null; 3 } 4 5 <!DOCTYPE html> 6 7 <html> 8 <head> 9 @* 10 Web客戶端用法說明 11 1. 配置路徑:$.connection("/myPreConnection1");需要與Startup中的對應 12 2. 常用的幾個方法有: 13 ① start:開啟連接 14 ② received:接受伺服器發送來的消息 15 ③ disconnected:連接中斷時調用 16 ④ error:連接發生錯誤的時嗲用 17 ④ stop:斷開連接 18 ⑤ send:發送消息 19 另外還有:connectionSlow、stateChanged、reconnecting、reconnected等等 20 3. 當前連接狀態有4種 21 connecting: 0(正在連接), connected: 1(正常連接), reconnecting: 2(正在重連), disconnected: 4 (掉線了) 22 *@ 23 <meta name="viewport" content="width=device-width" /> 24 <title>Index</title> 25 <script src="~/Scripts/jquery-3.3.1.min.js"></script> 26 <script src="~/Scripts/jquery.signalR-2.3.0.js"></script> 27 <script type="text/javascript"> 28 $(function () { 29 var conn = $.connection("/myPreConnection1"); 30 //一. 監控 31 //1. 接受伺服器發來的消息 32 conn.received(function (data) { 33 $("#j_Msg").append("<li>" + data + "</li>"); 34 }); 35 //2. 連接斷開的方法 36 conn.disconnected(function () { 37 $("#j_notice").html("連接中斷"); 38 }); 39 //3. 連接發生錯誤時候觸發 40 conn.error(function (data) { 41 $("#j_notice").html(data); 42 }); 43 //二. 主動事件 44 //1.建立連接 45 $("#j_connect").click(function () { 46 conn.start(function () { 47 $("#j_notice").html("連接成功"); 48 }); 49 }); 50 //2.斷開連接 51 $("#j_close").click(function () { 52 conn.stop(); 53 }); 54 //3.發送消息 55 $("#j_send").click(function () { 56 //發送消息之前要判斷連接狀態,conn.state有4中狀態 57 //connecting: 0(正在連接), connected: 1(正常連接), reconnecting: 2(正在重連), disconnected: 4 (掉線了) 58 console.log(conn.state); 59 if (conn.state == 1) { 60 conn.send({ receiveId: $("#j_receiveId").val(), msg: $("#j_content").val() }); 61 62 } else if (conn.state == 0) { 63 $("#j_notice").html("正在連接中,請稍等"); 64 } else if (conn.state == 2) { 65 $("#j_notice").html("正在重連,請稍等"); 66 } else if (conn.state == 4) { 67 $("#j_notice").html("掉線了,請重新連接"); 68 } 69 70 }); 71 72 }); 73 </script> 74 </head> 75 <body> 76 <div> 77 <div><span>提示:</span><span id="j_notice"></span></div> 78 <div style="margin-top:20px"> 79 <button id="j_connect">建立連接</button> 80 <button id="j_close">關閉連接</button> 81 </div> 82 <div style="margin-top:20px"> 83 <input type="text" value="" placeholder="請輸入接收人的標記" id="j_receiveId" /> 84 <input type="text" value="" placeholder="請輸入發送內容" id="j_content" /> 85 <button id="j_send">發送消息</button> 86 </div> 87 <div> 88 <ul id="j_Msg"></ul> 89 </div> 90 </div> 91 </body> 92 </html>View Code
(6). 運行效果
四. 分組的概念
1. PersistentConnection類中提供了一個 IConnectionGroupManager Groups的概念,即可以將不同用戶分到不同組裡,就好比QQ的中的討論組, 在這個組裡發信息,該組裡的所有人都能看到,但別的組是看不到的。並提供了兩個方法分別是
①. 加入組:Task Add(string connectionId, string groupName)
②. 移除組:Task Remove(string connectionId, string groupName)
IConnectionGroupManager下提供兩個針對組進行發送消息的方法
①. 針對單個組(可以去掉不發送的人):Task Send(string groupName, object value, params string[] excludeConnectionIds);
②. 針對多個組(可以去掉不發送的人):Task Send(IList<string> groupNames, object value, params string[] excludeConnectionIds);
註:一個客戶端可以同時加入多個組的,就好比qq,一個用戶你可以同時在多個討論組裡討論,相互不影響。
2. 需求背景:
有兩個房間,分別是room1和room2,將2個人加入到room1里,2兩個人加入到room2里,1個既加入room1且加入room2,測試向指定組發送消息和普通的群發消息。
測試頁面如下圖:
3. 先貼代碼後分析
實體類代碼

1 public class RoomData 2 { 3 /// <summary> 4 /// 房間名稱 5 /// </summary> 6 public string roomName { get; set; } 7 8 /// <summary> 9 /// 發送的消息 10 /// </summary> 11 public string msg { get; set; } 12 13 /// <summary> 14 /// 用來區分是進入房間,還是普通的發送消息 15 /// "enter":表示進入房間 16 /// "sendRoom":表示向某個組發送信息 17 /// "":表示普通的消息發送,不區分組的概念 18 /// </summary> 19 public string action { get; set; } 20 }View Code
伺服器端代碼

1 public class MyPresitentConnection2 : PersistentConnection 2 { 3 protected override Task OnConnected(IRequest request, string connectionId) 4 { 5 //提示自己進入成功 6 return Connection.Send(connectionId, "Welcome!"); 7 } 8 9 protected override Task OnReceived(IRequest request, string connectionId, string data) 10 { 11 //data是一個json對象 { roomName: "room2", action: "enter", msg: "" } 12 var model = JsonConvert.DeserializeObject<RoomData>(data); 13 if (model.action == "enter") 14 { 15 //表示建立組關係 16 this.Groups.Add(connectionId, model.roomName); 17 //提示自己進入房間成功 18 Connection.Send(connectionId, $"進入{model.roomName}房間成功"); 19 //向該組中除了當前人外,均發送歡迎消息 20 return this.Groups.Send(model.roomName, $"歡迎{connectionId}進入{model.roomName}房間", connectionId); 21 } 22 else if (model.action == "sendRoom") 23 { 24 //表示普通的按組發送信息(除了自己以外) 25 return this.Groups.Send(model.roomName, string.Format("用戶 {0} 發來消息: {1}", connectionId, model.msg), connectionId); 26 } 27 else 28 { 29 //表示普通的群發,不分組 30 return Connection.Broadcast(string.Format("用戶 {0} 發來消息: {1}", connectionId, model.msg), connectionId); 31 } 32 } 33 }View Code
Html代碼

1 @{ 2 Layout = null; 3 } 4 5 <!DOCTYPE html> 6 7 <html> 8 <head> 9 <meta name="viewport" content="width=device-width" /> 10 <title>Index</title> 11 <script src="~/Scripts/jquery-3.3.1.min.js"></script> 12 <script src