一. 承上聲明javascript
幾點介紹: html
1. PersistentConnection(永久鏈接)相對於Hubs模式,更加偏向底層,它的編程模式與WebSocket的寫法很相似,固定方法發送和接受,不能向Hub模式那樣 客戶端和服務端相互調用各自定義的方法。前端
2. 該模型主要用於:單個發件人、分組、廣播消息的簡單終結點。java
二. 從零開始搭建jquery
1. 新建MVC5項目,經過Nuget安裝:Microsoft.AspNet.SignalR程序集,安裝成功後以下圖:web
2. 新建一個永久鏈接模型類(MyPresitentConnection1),該類繼承了PersistentConnection,而且override幾個必要方法。編程
3. 新建一個OWIN Startup Class(Startup),並在Configuration方法中指定使用的通信模型的URl, 如: app.MapSignalR<MyPresitentConnection1>("/myPreConnection1"); json
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 }
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 }
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>
(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 }
服務器端代碼
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 }
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="~/Scripts/jquery.signalR-2.3.0.js"></script> 13 <script type="text/javascript"> 14 $(function () { 15 var conn = $.connection("/myPreConnection2"); 16 //一. 監控 17 //1. 接受服務器發來的消息 18 conn.received(function (data) { 19 $("#j_Msg").append("<li>" + data + "</li>"); 20 }); 21 //2. 鏈接斷開的方法 22 conn.disconnected(function () { 23 $("#j_notice").html("鏈接中斷"); 24 }); 25 //二. 主動事件 26 //1.創建鏈接 27 $("#j_connect").click(function () { 28 conn.start().done(function () { 29 $("#j_notice").html("鏈接成功"); 30 }); 31 }); 32 //2.斷開鏈接 33 $("#j_close").click(function () { 34 conn.stop(); 35 }); 36 //3.進入room1 37 $("#j_room1").click(function () { 38 conn.send({ roomName: "room1", action: "enter",msg:"" }); 39 }); 40 //4.進入room2 41 $("#j_room2").click(function () { 42 conn.send({ roomName: "room2", action: "enter", msg: "" }); 43 }); 44 //5. 給room1中的用戶發送消息 45 $("#j_sendRoom1").click(function () { 46 conn.send({ roomName: "room1", action: "sendRoom", msg: $('#j_content').val() }); 47 }); 48 //6. 給room2中的用戶發送消息 49 $("#j_sendRoom2").click(function () { 50 conn.send({ roomName: "room2", action: "sendRoom", msg: $('#j_content').val() }); 51 }); 52 //7. 普通羣發消息 53 $("#j_sendAll").click(function () { 54 conn.send({ roomName: "", action: "", msg: $('#j_content').val() }); 55 }); 56 57 }); 58 </script> 59 </head> 60 <body> 61 <div> 62 <div><span>提示:</span><span id="j_notice"></span></div> 63 <div style="margin-top:20px"> 64 <button id="j_connect">創建鏈接</button> 65 <button id="j_close">關閉鏈接</button> 66 </div> 67 <div style="margin-top:20px"> 68 <button id="j_room1">進入room1</button> 69 <button id="j_room2">進入room2</button> 70 </div> 71 <div style="margin-top:20px"> 72 <input type="text" value="" placeholder="請輸入發送內容" id="j_content" /> 73 <button id="j_sendRoom1">給room1發送消息</button> 74 <button id="j_sendRoom2">給room2發送消息</button> 75 <button id="j_sendAll">普通羣發</button> 76 </div> 77 <div> 78 <ul id="j_Msg"></ul> 79 </div> 80 </div> 81 </body> 82 </html>
代碼分析:
經過客戶端發送過來的action字段來區分幾種狀況。
① 當爲「enter」時,表示創建組關係,並提示本身進入房間成功,通知其餘人歡迎信息。
② 當爲「sendRoom」時,表示向指定組發送消息
③ 當爲空時,表示普通的向全部人發送消息,不區分組的概念
4. 效果展現(實在是難截圖啊)
5. 開始吐槽
原本框架默認提供一個組的概念,方便了咱們對一些業務的開發,是一好事,可是居然不能獲取每一個組內的connectionId列表,這。。。太坑了,不三不四的,還得本身記錄一下哪一個組中有哪些connectionId,坑啊,微軟baba真不知道你是怎麼想的。
五. 跨域請求
1. SignalR跨域請求的默認是關閉的,咱們能夠自行開啓,SignalR支持的跨域請求有兩種:
①:JSONP的模式,僅支持Get請求,須要服務器端配合,傳輸數據大小有限制
②:Cors模式,支持Post、Get等請求,須要在瀏覽器中加 【Access-Control-Allow-Origin:*】相似的配置
2. 開啓跨域請求的方式,詳見下面代碼:
1 public class Startup 2 { 3 public void Configuration(IAppBuilder app) 4 { 5 //1. JSONP的模式 6 //app.MapSignalR<MyPresitentConnection1>("/myPreConnection1", new Microsoft.AspNet.SignalR.ConnectionConfiguration() 7 //{ 8 // EnableJSONP = true 9 //}); 10 11 //2. Cors的模式(須要Nuget安裝:Microsoft.Owin.Cors程序集) 12 //app.Map("/myPreConnection1", (map) => 13 //{ 14 // map.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll); 15 //}); 16 17 //3. JSONP和Cors同時開啓 18 //app.Map("/myPreConnection1", (map) => 19 //{ 20 // //1. 開啓 jsonp 21 // map.RunSignalR<MyPresitentConnection1>(new Microsoft.AspNet.SignalR.HubConfiguration() { EnableJSONP = true }); 22 // //2. 開啓cors 23 // map.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll); 24 //}); 25 26 } 27 }
3. 跨域請求的做用是什麼,在後面章節和Hubs模型一塊兒介紹
六. 第三方調用
以上全部的代碼與通訊相關的代碼都寫在永久鏈接類中,但在開發中常常會遇到,我要在控制器中的某個方法中調用相應的方法,發給用戶信息,這個時候怎麼辦呢?
能夠經過:GlobalHost.ConnectionManager.GetConnectionContext<MyPresitentConnection1>();來獲取永久鏈接類,而後調用相應的方法。
代碼以下:
1 /// <summary> 2 /// 向全部人發送消息 3 /// </summary> 4 /// <param name="msg">發送的信息</param> 5 public string MySendAll(string msg) 6 { 7 string myConnectionId = Session["connectionId"].ToString(); 8 //PersistentConnection模式 9 var perConnection = GlobalHost.ConnectionManager.GetConnectionContext<MyPresitentConnection1>(); 10 perConnection.Connection.Broadcast(msg); 11 return "ok"; 12 }
關於具體結合業務的樣例在下一節的Hub的例子詳細編寫,原理都同樣。
七. 總結
!