现在的位置: 首页 > 综合 > 正文

SignalR实现服务器推送信息:广播与“组播”

2014年05月14日 ⁄ 综合 ⁄ 共 3854字 ⁄ 字号 评论关闭
    SignaR可以用来实现服务器向客户端推送信息,但是SignalR的每个Hub的生命周期很短,不能长期停留在内存里。因此,如果服务器想要对客户端推送消息时,应该采用signalR的一个“全局”的上下文来实现。此功能的应用背景是:服务器的数据发生了更新,它希望向所有的用户、某个分组的用户或者是单个用户发送推送消息,而不是由客户端发送请求来检测数据是否有更新。这才是真正的SignalR要做的事!

在Asp.Net有关SignalR的介绍里有提到向所有用户发送推送消息的实现方式(参见:http://www.asp.net/signalr/overview/signalr-20/getting-started-with-signalr-20/tutorial-server-broadcast-with-signalr-20)。但是向分组的用户或单个用户发送消息的实现方式,并没有提供。本文将说明如何向分组推送消息,其他的两种实现很相似。

另外,在我发现Signalr有提供全局机制之前,我采用了让一个Hub长期停留在内存的方式实现。大致意思为:
    我需要实现一个Hub,它可以动态地检测数据库数据的变化,然后推送给前端相关的用户。如果直接在继承Hub的类里实现,并调用的话,也就意味着由SignalR创建的所有Hub都要长时间地停留在内存里,而且由于我的推送信息是根据组(Group)进行的。结果导致的问题就是:有太多的Hub停留在内存了;而且针对某个分组的消息,会因为有多个Hub(每个Hub的实现是一样的)而发送多次。
    后来,我想到了一个解决这个的办法,就是我只在内存里保留一个Hub的实例。这样就不会出现一条消息多次发送的问题了。具体的做法是:  
    当客户端第一次新建一个Hub时,我就将这个Hub的实例保存起来。当其他的客户端再次新建Hub时,这些Hub不再保存。当这些客户端要提供注册的分组信息时,我就直接将这些分组信息注册到我保存的那个Hub去,而我的所有推送信息只在这个Hub里发出。
    老实说,系统已经运行很久了,这种方式还一直正常地使用着,没出现什么问题。不过,既然SignalR有提供全局的实现,那么,我们还是采用它提供的实现为好。

一、前置条件:

二、创建Hub Class
    实现一个Hub以及一个Ticker。Hub的实现很简单,就是要求将当前的Hub连接注册到Ticker上去。Ticker采用单例实现。具体代码如下:
 
 public class ChatHub : Hub
    {
        private readonly ChatTicker ticker;

        public ChatHub()
        {
            ticker = ChatTicker.Instance;
        }

        public void Send(string group, string username)
        {
            //注册到全局
            ticker.GlobalContext.Groups.Add(Context.ConnectionId, group);
            Clients.All.broadcastMessage(group, "user register:" + username);
        }
    }

    public class ChatTicker
    {
        #region 实现一个单例

        private static readonly ChatTicker _instance =
            new ChatTicker(GlobalHost.ConnectionManager.GetHubContext<ChatHub>());

        private readonly IHubContext m_context;

        private ChatTicker(IHubContext context)
        {
            m_context = context;
            //这里不能直接调用Sender,因为Sender是一个不退出的“死循环”,否则这个构造函数将不会退出。
            //其他的流程也将不会再执行下去了。所以要采用异步的方式。
            Task.Run(() => Sender());
        }

        public IHubContext GlobalContext
        {
            get { return m_context; }
        }

        public static ChatTicker Instance
        {
            get { return _instance; }
        }

        #endregion

        public void Sender()
        {
            int count = 0;
            while (true)
            {
                Thread.Sleep(500);
                int tag = count%2;
                //动态绑定前端的js函数 broadcaseMessage
                m_context.Clients.Group(tag + "").broadcastMessage("group is:" + tag, "current count:" + count);
                count++;
            }
        }
    }

三、前端测试代码
前端通过调用 Sender进行用户的监听注册,后台通过broadcaseMessage进行动态绑定。
<!DOCTYPE html>
<html>
<head>
    <title>SignalR Simple Chat</title>
    <style type="text/css">
        .container {
            background-color: #99CCFF;
            border: thick solid #808080;
            padding: 20px;
            margin: 20px;
        }
    </style>
</head>
<body>
    <div class="container">
        <label>input your name:</label>
        <input type="text" id="message" />
        <input type="button" id="sendmessage" value="Register" />
        <input type="hidden" id="displayname" />
        <ul id="discussion"></ul>
    </div>
    <!--Script references. -->
    <!--Reference the jQuery library. -->
    <script src="Scripts/jquery-1.6.4.min.js"></script>
    <!--Reference the SignalR library. -->
    <script src="Scripts/jquery.signalR-2.0.2.min.js"></script>
    <!--Reference the autogenerated SignalR hub script. -->
    <script src="signalr/hubs"></script>
    <!--Add script to update the page and send messages.-->
    <script type="text/javascript">
        $(function () {
            // Declare a proxy to reference the hub.
            $.connection.hub.logging = true;
            var chat = $.connection.chatHub;
            // Create a function that the hub can call to broadcast messages.
            chat.client.broadcastMessage = function (name, message) {
                // Html encode display name and message.
                var encodedName = $('<div />').text(name).html();
                var encodedMsg = $('<div />').text(message).html();
                // Add the message to the page.
                $('#discussion').append('<li><strong>' + encodedName
                    + '</strong>:  ' + encodedMsg + '</li>');
            };
            // Get the user name and store it to prepend to messages.
            $('#displayname').val(prompt('Enter your group, choose: 0 or 1:', ''));
            // Set initial focus to message input box.
            $('#message').focus();
            // Start the connection.
            $.connection.hub.start().done(function () {
                $('#sendmessage').click(function () {
                    // Call the Send method on the hub.
                    chat.server.send($('#displayname').val(), $('#message').val());
                    // Clear text box and reset focus for next comment.
                    $('#message').val('').focus();
                });
            });
        });
    </script>
</body>
</html>
四、测试:
前端输入分组0或1,并输入用户名。点击注册之后可以见到:

抱歉!评论已关闭.