专业网站建设品牌,十四年专业建站经验,服务6000+客户--广州京杭网络
免费热线:400-683-0016      微信咨询  |  联系我们

通过三个DEMO学会SignalR的三种实现方式

当前位置:网站建设 > 技术支持
资料来源:网络整理       时间:2023/2/14 0:41:32       共计:3614 浏览

一、理解SignalR

ASP .NET SignalR 是一个ASP .NET 下的类库,可以在ASP .NET 的Web项目中实现实时通信(即:客户端(Web页面)和服务器端可以互相实时的通知消息及调用方法),SignalR有三种传输模式:LongLooping(长轮询)、WebSocket(HTML5的WEB套接字)、Forever Frame(隐藏框架的长请求连接),可以在WEB客户端显式指定一种或几种,也可以采取默认(推荐),若采取默认,SignalR会根据浏览器的环境自动选择合适的传输方式。

二、SignalR的三种实现方式

第一种:采用集线器类(Hub)+非自动生成代理模式:服务端与客户端分别定义的相对应的方法,客户端通过代理对象调用服务端的方法,服务端通过IHubConnectionContext回调客户端的方法,客户端通过回调方法接收结果。

之前我写过一篇文章《分享一个基于长连接+长轮询+原生的JS及AJAX实现的多人在线即时交流聊天室》,是通过长轮询+长连接的方式来实现的在线多人聊天室功能,从代码量来看就知道实现起来并不简单,而如今有了SignalR,会简单很多,我这里使用SignalR再来写一个简单的在线多人聊天室示例,以便大家快速掌握SignalR。

DEMO - 1 示例代码如下:

服务端:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 //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);         }       } }

 

1 2 3 4 5 6 7 8 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客户端:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 <!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)+自动生成代理模式

DEMO - 2 示例代码如下:

服务端与DEMO 1相同,无需改变

客户端:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 <!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 示例代码如下:

服务端:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 //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客户端:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 <!-- 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使用方式整理):


版权说明:
本网站凡注明“广州京杭 原创”的皆为本站原创文章,如需转载请注明出处!
本网转载皆注明出处,遵循行业规范,如发现作品内容版权或其它问题的,请与我们联系处理!
欢迎扫描右侧微信二维码与我们联系。
·上一条:SignalR实现消息推送,包括私聊、群聊、在线所有人接收消息(源码) | ·下一条:Signalr实现消息推送

Copyright © 广州京杭网络科技有限公司 2005-2024 版权所有    粤ICP备16019765号 

广州京杭网络科技有限公司 版权所有