服务端主动推送,其实我们一直这么用,比如:当我们从客户端调用服务端方法时,绝大部分服务端也会有相应的响应一个客户端方法。

而把它作为独立一部分来讲,是因为我想通过一个示例,来表现服务端主动推送的能力。对于大访问量而言批量推送也不能按常规的做法,关于这一点,我想会再发表新的文章来说明。

现在我制作一下监控服务器CPU状态的示例,这里我会采用 PersistentConnection 来创建一个消息通道。SignalR分别支持 PersistentConnectionHub API接口两种,他们两者有者很大的不同,同样也会在其他文章说明。

一、CPU状态模型类

Usage 占用率、Time当时间。

public class CPUUsageInfo
{
    public double Usage { get; set; }
    public DateTime Time { get; set; }
}

二、创建一个继承于 PersistentConnection 消息连接通道

public class PerfmonConnection : PersistentConnection
{
}

三、注册连接通道

目前2X版本,必须使用 app.MapSignalR 来注册连接通道,他提供一个可以指定连接对象的泛型入口,参数分别是连接时用的服务器地址 /perfmon,以及我们需要一些属性配置。

app.MapSignalR<PerfmonConnection>("/perfmon", new ConnectionConfiguration()
{
    EnableJSONP = false
});

四、读取服务器CPU使用率

这里我是在 Application_Start 上做一个 Task 任务。其中我们可以 GlobalHost.ConnectionManager 来获取我们创建的连接消息实例对象,紧接者再通过 Broadcast 进行通知。完整代码如下:

Task.Factory.StartNew(() =>
{
    IConnection connection = GlobalHost.ConnectionManager.GetConnectionContext<PerfmonConnection>().Connection;

    var counter = new PerformanceCounter();
    counter.CategoryName = "Processor";
    counter.CounterName = "% Processor Time";
    counter.InstanceName = "_Total";

    while (true)
    {
        var item = new CPUUsageInfo()
        {
            Time = DateTime.Now,
            Usage = Math.Round(counter.NextValue(), 2)
        };

        connection.Broadcast(item);

        System.Threading.Thread.Sleep(1000);
    }
});

注意这里 connection.Broadcast 并无指明到底是谁,换言之是对所有连接的客户端进行群发,而关于权限等可以在 PerfmonConnection 重写相应的方法来满足我们的需求。

五、客户端脚本

$.connection('/perfmon').received(function (item) { 
    console.log(item);
}).start();

非常简单的三行代码,这跟hub连接不一样,PersistentConnection 相对于 Hub 更加底层,也就是说 Hub 是继承于 PersistentConnection 并添加一些更加复杂但对于我们更加易用的方法。

为了让数据表现上更好看,我这里引用 Highcharts 做一下修饰,所以完整的客户端脚本是:

<div id="container" style="min-width:700px;height:400px"></div>
<script type="text/javascript" src="http://cdn.hcharts.cn/highstock/2.0.3/highstock.js"></script>
Highcharts.setOptions({
    global: {
        useUTC: false
    }
});

var chartOptions = {
    chart: {
        renderTo: 'container',
        animation: Highcharts.svg, // don't animate in old IE               
        marginRight: 10
    },
    rangeSelector: {
        buttons: [{
            count: 1,
            type: 'minute',
            text: '1M'
        }, {
            count: 5,
            type: 'minute',
            text: '5M'
        }, {
            type: 'all',
            text: 'All'
        }],
        inputEnabled: false,
        selected: 0
    },

    title: {
        text: 'CPU usage history'
    },

    exporting: {
        enabled: false
    },

    series: [{
        name: 'CPU Usage in %',
        data: (function () {
            // generate an array of random data
            var data = [], time = (new Date()).getTime(), i;

            for (i = -999; i <= 0; i++) {
                data.push([
                    time + i * 1000,
                    Math.round(Math.random() * 100)
                ]);
            }
            return data;
        })()
    }]
};

var chart = new Highcharts.StockChart(chartOptions);

var perfmonConn = $.connection('/perfmon').received(function (item) {
    chart.series[0].addPoint([(new Date(item.Time)).getTime(), item.Usage], true, true);
}).start();

六、Using a Hub instance not created by the HubPipeline is unsupported 错误

假设有个 MessgeHub 下有人 Get 服务端方法,这个方法会结果是向所有用户主动推送一个数值。按正常逻辑的方式我们可能会这么写:

new MessageHub().Get();

很好,你就会收到这个异常,明确的说你是并不支持这种hub实例,因为他无法创建 HubPipeline,由于 MessageHub 下会继承 BaseHub 且会有一个叫 Context 属性并且可以重新给他设定一个实例;这样我想可以通过自定义一个 HubPipeline 并赋值给它,可这问题就变得非常复杂。

而往往这种主动推送会发生在不同情况下,所以最好的做好是将要推送的内容最一层封装,然后在我们需要主送向客户端推送时,可以这么做:

 Microsoft.AspNet.SignalR.GlobalHost.ConnectionManager.GetHubContext<MessgeHub>().Clients.All.Get(1);

总结

上面示例有没有发现一个很好玩的事情,你看 PerfmonConnection 简单到一句代码也不需要写,唯一需要创建一个 Task 任务做一下数据包裹,然后发给客户端接收。因此如果你想让现有功能享受SignalR带来的双向通信好处,那么压根就不需要写太多代码,是不是很愉快呢?

查看 SignalR系列文章