1、理解SignalRjavascript
ASP .NET SignalR 是一個ASP .NET 下的類庫,能夠在ASP .NET 的Web項目中實現實時通訊(即:客戶端(Web頁面)和服務器端能夠互相實時的通知消息及調用方法),SignalR有三種傳輸模式:LongLooping(長輪詢)、WebSocket(HTML5的WEB套接字)、Forever Frame(隱藏框架的長請求鏈接),能夠在WEB客戶端顯式指定一種或幾種,也能夠採起默認(推薦),若採起默認,SignalR會根據瀏覽器的環境自動選擇合適的傳輸方式。css
2、SignalR的三種實現方式html
第一種:採用集線器類(Hub)+非自動生成代理模式:服務端與客戶端分別定義的相對應的方法,客戶端經過代理對象調用服務端的方法,服務端經過IHubConnectionContext回調客戶端的方法,客戶端經過回調方法接收結果。java
以前我寫過一篇文章《分享一個基於長鏈接+長輪詢+原生的JS及AJAX實現的多人在線即時交流聊天室》,是經過長輪詢+長鏈接的方式來實現的在線多人聊天室功能,從代碼量來看就知道實現起來並不簡單,而現在有了SignalR,會簡單不少,我這裏使用SignalR再來寫一個簡單的在線多人聊天室示例,以便你們快速掌握SignalR。jquery
DEMO - 1 示例代碼以下:瀏覽器
服務端:服務器
//Startup類文件 using System; using System.Threading.Tasks; using Microsoft.Owin; using Owin; using Microsoft.AspNet.SignalR; [assembly: OwinStartup(typeof(TestWebApp.Models.Startup))] namespace TestWebApp.Models { public class Startup { public void Configuration(IAppBuilder app) { app.MapSignalR(); } } } //ChatHub類文件 using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Web; namespace TestWebApp.Models { [HubName("chat")] public class ChatHub : Hub { public static ConcurrentDictionary<string, string> OnLineUsers = new ConcurrentDictionary<string, string>(); [HubMethodName("send")] public void Send(string message) { string clientName = OnLineUsers[Context.ConnectionId]; message = HttpUtility.HtmlEncode(message).Replace("\r\n", "<br/>").Replace("\n", "<br/>"); Clients.All.receiveMessage(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), clientName, message); } [HubMethodName("sendOne")] public void Send(string toUserId, string message) { string clientName = OnLineUsers[Context.ConnectionId]; message = HttpUtility.HtmlEncode(message).Replace("\r\n", "<br/>").Replace("\n", "<br/>"); Clients.Caller.receiveMessage(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), string.Format("您對 {1}", clientName, OnLineUsers[toUserId]), message); Clients.Client(toUserId).receiveMessage(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), string.Format("{0} 對您", clientName), message); } public override System.Threading.Tasks.Task OnConnected() { string clientName = Context.QueryString["clientName"].ToString(); OnLineUsers.AddOrUpdate(Context.ConnectionId, clientName, (key, value) => clientName); Clients.All.userChange(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), string.Format("{0} 加入了。", clientName), OnLineUsers.ToArray()); return base.OnConnected(); } public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled) { string clientName = Context.QueryString["clientName"].ToString(); Clients.All.userChange(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), string.Format("{0} 離開了。", clientName), OnLineUsers.ToArray()); OnLineUsers.TryRemove(Context.ConnectionId, out clientName); return base.OnDisconnected(stopCalled); } } } public ActionResult Index() { ViewBag.ClientName = "聊客-" + Guid.NewGuid().ToString("N"); var onLineUserList = ChatHub.OnLineUsers.Select(u => new SelectListItem() { Text = u.Value, Value = u.Key }).ToList(); onLineUserList.Insert(0, new SelectListItem() { Text = "-全部人-", Value = "" }); ViewBag.OnLineUsers = onLineUserList; return View(); }
WEB客戶端:app
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <meta charset="utf-8" /> <title>聊天室</title> <script src="~/Scripts/jquery-1.6.4.min.js" type="text/javascript"></script> <script src="~/Scripts/jquery.signalR-2.2.0.min.js" type="text/javascript"></script> <style type="text/css"> #chatbox { width: 100%; height: 500px; border: 2px solid blue; padding: 5px; margin: 5px 0px; overflow-x: hidden; overflow-y: auto; } .linfo { } .rinfo { text-align: right; } </style> <script type="text/javascript"> $(function () { var clientName = $("#clientname").val(); var eChatBox = $("#chatbox"); var eUsers = $("#users"); var conn = $.hubConnection(); conn.qs = { "clientName": clientName }; conn.start().done(function () { $("#btnSend").click(function () { var toUserId = eUsers.val(); if (toUserId != "") { chat.invoke("sendOne", toUserId, $("#message").val()) .done(function () { //alert("發送成功!"); $("#message").val("").focus(); }) .fail(function (e) { alert(e); $("#message").focus(); }); } else { chat.invoke("send", $("#message").val()) .done(function () { //alert("發送成功!"); $("#message").val("").focus(); }) .fail(function (e) { alert(e); $("#message").focus(); }); } }); }); var chat = conn.createHubProxy("chat"); chat.on("receiveMessage", function (dt, cn, msg) { var clsName = "linfo"; if (cn == clientName || cn.indexOf("您對") >= 0) clsName = "rinfo"; eChatBox.append("<p class='" + clsName + "'>" + dt + " <strong>" + cn + "</strong> 說:<br/>" + msg + "</p>"); eChatBox.scrollTop(eChatBox[0].scrollHeight); }); chat.on("userChange", function (dt, msg, users) { eChatBox.append("<p>" + dt + " " + msg + "</p>"); eUsers.find("option[value!='']").remove(); for (var i = 0; i < users.length; i++) { if (users[i].Value == clientName) continue; eUsers.append("<option value='" + users[i].Key + "'>" + users[i].Value + "</option>") } }); }); </script> </head> <body> <h3>大衆聊天室</h3> <div id="chatbox"> </div> <div> <span>聊天名稱:</span> @Html.TextBox("clientname", ViewBag.ClientName as string, new { @readonly = "readonly", style = "width:300px;" }) <span>聊天對象:</span> @Html.DropDownList("users", ViewBag.OnLineUsers as IEnumerable<SelectListItem>) </div> <div> @Html.TextArea("message", new { rows = 5, style = "width:500px;" }) <input type="button" value="發送消息" id="btnSend" /> </div> </body> </html>
服務端與客戶端代碼都比較簡單,網上相關的說明也有,這裏就再也不解說了,只說一下這種方式JS端調用服務端方法採用:chat.invoke,而被服務端回調的方法則採用:chat.on (這裏的chat是createHubProxy建立得來的)框架
第二種:採用集線器類(Hub)+自動生成代理模式ide
DEMO - 2 示例代碼以下:
服務端與DEMO 1相同,無需改變
客戶端:
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <meta charset="utf-8" /> <title>聊天室</title> <script src="~/Scripts/jquery-1.6.4.min.js" type="text/javascript"></script> <script src="~/Scripts/jquery.signalR-2.2.0.min.js" type="text/javascript"></script> <script src="~/signalr/hubs" type="text/javascript"></script> <style type="text/css"> #chatbox { width: 100%; height: 500px; border: 2px solid blue; padding: 5px; margin: 5px 0px; overflow-x: hidden; overflow-y: auto; } .linfo { } .rinfo { text-align: right; } </style> <script type="text/javascript"> $(function () { var clientName = $("#clientname").val(); var eChatBox = $("#chatbox"); var eUsers = $("#users"); var chat = $.connection.chat; $.connection.hub.qs = { "clientName": clientName }; chat.state.test = "test"; chat.client.receiveMessage = function (dt, cn, msg) { var clsName = "linfo"; if (cn == clientName || cn.indexOf("您對")>=0) clsName = "rinfo"; eChatBox.append("<p class='" + clsName + "'>" + dt + " <strong>" + cn + "</strong> 說:<br/>" + msg + "</p>"); eChatBox.scrollTop(eChatBox[0].scrollHeight); } chat.client.userChange = function (dt, msg, users) { eChatBox.append("<p>" + dt + " " + msg + "</p>"); eUsers.find("option[value!='']").remove(); for (var i = 0; i < users.length; i++) { if (users[i].Value == clientName) continue; eUsers.append("<option value='" + users[i].Key + "'>" + users[i].Value + "</option>") } } $.connection.hub.start().done(function () { $("#btnSend").click(function () { var toUserId = eUsers.val(); if (toUserId != "") { chat.server.sendOne(toUserId, $("#message").val()) .done(function () { //alert("發送成功!"); $("#message").val("").focus(); }) .fail(function (e) { alert(e); $("#message").focus(); }); } else { chat.server.send($("#message").val()) .done(function () { //alert("發送成功!"); $("#message").val("").focus(); }) .fail(function (e) { alert(e); $("#message").focus(); }); } }); }); }); </script> </head> <body> <h3>大衆聊天室</h3> <div id="chatbox"> </div> <div> <span>聊天名稱:</span> @Html.TextBox("clientname", ViewBag.ClientName as string, new { @readonly = "readonly", style = "width:300px;" }) <span>聊天對象:</span> @Html.DropDownList("users", ViewBag.OnLineUsers as IEnumerable<SelectListItem>) </div> <div> @Html.TextArea("message", new { rows = 5, style = "width:500px;" }) <input type="button" value="發送消息" id="btnSend" /> </div> </body> </html>
上述代碼中特別須要注意的是,須要引用一個「不存在的JS目錄」:<script src="~/signalr/hubs" type="text/javascript"></script>,爲何要打引號,是由於咱們在寫代碼的時候是不存在的,而當運行後就會自動生成signalr的代理腳本,這就是與非自動生成代理腳本最根本的區別,也正是由於這個自動生成的腳本,咱們能夠在JS中更加方便的調用服務端方法及定義回調方法,調用服務端方法採用:chat.server.XXX,而被服務端回調的客戶端方法則採用:chat.client.XXX
看一下上述兩種的運行效果截圖吧:
第三種:採用持久化鏈接類(PersistentConnection)
DEMO - 3 示例代碼以下:
服務端:
//Startup類: using System; using System.Threading.Tasks; using Microsoft.Owin; using Owin; using Microsoft.AspNet.SignalR; [assembly: OwinStartup(typeof(TestWebApp.Models.Startup))] namespace TestWebApp.Models { public class Startup { public void Configuration(IAppBuilder app) { app.MapSignalR<MyConnection>("/MyConnection"); } } } //MyConnection類: using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Web; using Microsoft.AspNet.SignalR; namespace TestWebApp.Models { public class MyConnection : PersistentConnection { private static List<string> monitoringIdList = new List<string>(); protected override Task OnConnected(IRequest request, string connectionId) { bool IsMonitoring = (request.QueryString["Monitoring"] ?? "").ToString() == "Y"; if (IsMonitoring) { if (!monitoringIdList.Contains(connectionId)) { monitoringIdList.Add(connectionId); } return Connection.Send(connectionId, "ready"); } else { if (monitoringIdList.Count > 0) { return Connection.Send(monitoringIdList, "in_" + connectionId); } else { return Connection.Send(connectionId, "nobody"); } } } protected override Task OnReceived(IRequest request, string connectionId, string data) { if (monitoringIdList.Contains(connectionId)) { return Connection.Send(data, "pass"); } return null; } protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled) { if (!monitoringIdList.Contains(connectionId)) { return Connection.Send(monitoringIdList, "out_" + connectionId); } return null; } } }
WEB客戶端:
<!-- MonitoringPage.cshtml 監控管理頁面--> <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>MonitoringPage</title> <script src="~/Scripts/jquery-1.6.4.min.js" type="text/javascript"></script> <script src="~/Scripts/jquery.signalR-2.2.0.min.js" type="text/javascript"></script> <style type="text/css"> table { border:1px solid #808080; width:600px; } td { border:1px solid #808080; padding:3px; } .odd{ background-color: #bbf;} .even{ background-color:#ffc; } .non-temptr { display:none; } </style> <script type="text/javascript"> $(function () { $("#userstable tbody tr:odd").addClass("odd"); $("#userstable tbody tr:even").addClass("even"); var conn = $.connection("/MyConnection", {"Monitoring":"Y"}); conn.start().done(function () { $("#userstable").delegate("button.pass", "click", function () { var rid = $(this).parent("td").prev().attr("data-rid"); conn.send(rid); var tr = $(this).parents("tr"); tr.remove(); }); }).fail(function (msg) { alert(msg); }); conn.received(function (msg) { if (msg == "ready") { $("#spstatus").html("監控服務已就緒"); return; } else if (msg.indexOf("in_") == 0) { var tr = $(".non-temptr").clone(true); tr.removeClass("non-temptr"); var td = tr.children().first(); var rid = msg.toString().substr("in_".length); td.html(rid + "進入被監控頁面,是否容許?"); td.attr("data-rid", rid); $("#userstable tbody").append(tr); } else { var rid = msg.toString().substr("out_".length); $("td[data-rid=" + rid + "]").parent("tr").remove(); } }); }); </script> </head> <body> <div> 如下是實時監控到進入EnterPage頁面的用戶狀況:(服務情況:<strong><span id="spstatus"></span></strong>) </div> <table id="userstable"> <tr> <td>用戶進入消息</td> <td>授 權</td> </tr> <tr class="non-temptr"> <td></td> <td style="width:100px"><button class="pass">容許</button></td> </tr> </table> </body> </html> <!-- EnterPage.cshtml 監控受限頁面--> <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>EnterPage</title> <script src="~/Scripts/jquery-1.6.4.min.js" type="text/javascript"></script> <script src="~/Scripts/jquery.signalR-2.2.0.min.js" type="text/javascript"></script> </head> <body> <script type="text/javascript"> $(function () { var conn = $.connection("/MyConnection"); conn.start().fail(function (msg) { alert(msg); }); conn.received(function (data) { if (data == "pass") { $("#msg").html("管理員已審覈經過,能夠進入瀏覽詳情。"); setTimeout(function () { self.location = "http://www.zuowenjun.cn"; }, 3000); } else { $("#msg").html("無管理員在線,請稍候再從新進入該頁面。"); } }); }); </script> <div id="msg"> 該頁面瀏覽受限,已自動將您的瀏覽請求發給管理員,請稍候。。。 </div> </body> </html>
上述代碼能夠看出與採用Hub(集線器類)的不一樣之處,一是:Startup.Configuration中是須要指定app.MapSignalR<MyConnection>("/MyConnection"),二是需實現繼承自PersistentConnection類的自定義的持久化鏈接類,在這個鏈接中能夠重寫:OnConnected、OnDisconnected、OnReceived、OnReconnected、ProcessRequest方法,同時有幾個重要的屬性成員Connection、Groups,服務端發消息給客戶端採用:Connection.Broadcast(廣播,全部客戶端均可以收到消息),Connection.Send(發送給指定的客戶端)
運行效果以下截圖示:
SignalR支持額外附加:QueryString、Cookie、State,具體的客戶端設置與服務端接收請見上面的代碼,同時也能夠參見以下其它博主總結的表格(SignalR的Javascript客戶端API使用方式整理):