Asp.Net下SignalR的三种实现方式

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

              服务端与客户端分别定义的相对应的方法,客户端通过代理对象调用服务端的方法,服务端通过IHubConnectionContext回调客户端的方法,客户端通过回调方法接收结果。

服务端代码:

//StartUp.cs  采用OWIN启动方式
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); } } }

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 {

            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 = "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)+自动生成代理模式

服务端代码:与第一种相同,无需改变。

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>

    <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 = "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)

服务端代码:

//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端代码:

<!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="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(发送给指定的客户端)

原文地址:https://www.cnblogs.com/xtxk110/p/12896208.html