• SignalR


    一、理解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 示例代码如下:

    服务端:

    //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("
    ", "<br/>").Replace("
    ", "<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("
    ", "<br/>").Replace("
    ", "<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客户端:

    <!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 {
                 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 = "300px;" })
            <span>聊天对象:</span>
            @Html.DropDownList("users", ViewBag.OnLineUsers as IEnumerable<SelectListItem>)
        </div>
        <div>
            @Html.TextArea("message", new { rows = 5, style = "500px;" })
            <input type="button" value="发送消息" id="btnSend" />
        </div>
    </body>
    </html>

    服务端与客户端代码都比较简单,网上相关的说明也有,这里就不再解说了,只说一下这种方式JS端调用服务端方法采用:chat.invoke,而被服务端回调的方法则采用:chat.on (这里的chat是createHubProxy创建得来的)

    第二种:采用集线器类(Hub)+自动生成代理模式

    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 {
                 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 = "300px;" })
            <span>聊天对象:</span>
            @Html.DropDownList("users", ViewBag.OnLineUsers as IEnumerable<SelectListItem>)
        </div>
        <div>
            @Html.TextArea("message", new { rows = 5, style = "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;
                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="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使用方式整理):

  • 相关阅读:
    概率算法实现八皇后问题-cpp
    交互式多媒体图书平台的设计
    Map容器
    序列容器和容器适配器
    ubuntu18.04 基于VSCode的C++环境搭建
    工程化编程实战callback接口学习
    stl_string
    通过filebeat的modules搜集nginx日志
    gitlab-runner安装配置
    EFK搜集MySQL慢日志
  • 原文地址:https://www.cnblogs.com/sylone/p/11399564.html
Copyright © 2020-2023  润新知