zoukankan      html  css  js  c++  java
  • 通过三个DEMO学会SignalR的三种实现方式 转载https://www.cnblogs.com/zuowj/p/5674615.html

    一、理解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<stringstring> OnLineUsers = new ConcurrentDictionary<stringstring>();
     
            [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);
            }
     
        }
    }
    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 {
                 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 stringnew { @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相同,无需改变

    客户端:

    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 {
                 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 示例代码如下:

    服务端:

    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;
                600px;
            }
            td {
                border:1px solid #808080;
                padding:3px;
            }
            .odd{ }
            .even{ }
            .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使用方式整理):

  • 相关阅读:
    不可或缺 Windows Native (15)
    不可或缺 Windows Native (14)
    不可或缺 Windows Native (13)
    不可或缺 Windows Native (12)
    不可或缺 Windows Native (11)
    不可或缺 Windows Native (10)
    不可或缺 Windows Native (9)
    不可或缺 Windows Native (8)
    不可或缺 Windows Native (7)
    不可或缺 Windows Native (6)
  • 原文地址:https://www.cnblogs.com/Jeely/p/11058373.html
Copyright © 2011-2022 走看看