SignalR系列教程:服务器广播与主动数据推送
本篇是本系列入门篇的最后一遍,由于工作关系,接触SignalR的时间不是很多。等下次有空的话我会写一个利用“SignalR”开发一个在线聊天室的系列博文。近期的话我更偏向于更新框架设计相关的文章,到时候我会在文章中分享我在工作中开发的“日志框架”、“缓存框架”,“分布式下载框架”等。有兴趣的朋友可以关注我,一起交流。
本篇博文参考:https://www.asp.net/signalr/overview/getting-started/tutorial-server-broadcast-with-signalr
本教程演示如何创建一个 web 应用程序使用 ASP.NET SignalR 2 提供的服务器广播功能。服务器广播意味着发送到客户端的通信由服务器启动。我们之前聊天室的项目是一个用户提交数据后,服务器接收到消息,然后把消息广播给当前所有的用户。如下图

本教程所讲的恰恰相反,我们是由服务器自动把消息推送给当前所有用户。如股票信息显示:

概述
在本教程中,我们将会创建一个股票行情自动收录的实时应用程序,在其中您想要定期"推"送数据,通知从服务器到所有连接的客户端。在本教程的第一部分,你将从头开始创建该应用程序的简化的版本。在本教程的其余部分中,您会安装 NuGet 包,其中包含额外的功能,并审查这些功能的代码。
创建项目
我们依然新建一个空项目,并使用“程序包管理控制台”执行“Install-PackAge Microsoft.AspNet.SignaLR”安装最新版本的SignaLR。
安装完成后会自动打开“readme.txt”文件,文中告诉我们要新建一个Startup并注册SignalR。原文如下
To enable SignalR in your application, create a class called Startup with the following: using Microsoft.Owin;
using Owin;
using MyWebApplication; namespace MyWebApplication
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapSignalR();
}
}
}
我们按照它的来,新建一个Startup然后在Configuration中注册SignalR路由。
创建代码
我们创建Stock用来存放股票详细信息
public class Stock
{
private decimal _price; public string Symbol { get; set; } public decimal Price
{
get
{
return _price;
}
set
{
if (_price == value)
{
return;
} _price = value; if (DayOpen == 0)
{
DayOpen = _price;
}
}
} public decimal DayOpen { get; private set; } public decimal Change
{
get
{
return Price - DayOpen;
}
} public double PercentChange
{
get
{
return (double)Math.Round(Change / Price, 4);
}
}
}
创建StockTicker和StockTickerHub的类
我们将在StockTickerHub类中定义和JS交互的代码。我们需要维护股票数据的更新和删除,但是我们不能在StockTickerHub类中进行操作,因为StockTickerHub类是不保存数据的,如果我们把股票的CURD代码放在StockTickerHub中可能会造成我们的数据丢失。我们利用VS添加新项选择集线器V2,并替换成以下代码
[HubName("stockTickerMini")]
public class StockTickerHub : Hub
{
private readonly StockTicker _stockTicker;
public StockTickerHub() : this(StockTicker.Instance) { }
public StockTickerHub(StockTicker stockTicker)
{
_stockTicker = stockTicker;
}
public IEnumerable<Stock> GetAllStocks()
{
return _stockTicker.GetAllStocks();
}
}
在StockTickerHub类中,我们公开了一个GetAllStocks的方法,当客户端连接成功后我们将调用“GetAllStocks”方法用来显示股票信息。HubName标签是一个别名,按照传统我们连接需要用StockTickerHub,但是我们加了HubName后,前段就可以通过stockTickerMini来创建连接。
我们新建一个StockTicker类,把替换成以下代码
public class StockTicker
{
private static readonly Lazy<StockTicker> _instance = new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients)); /// <summary>
/// ConcurrentDictionary 表示可以由多个线程访问的安全集合
/// 用来存放股票代码以及股票价格
/// </summary>
private readonly ConcurrentDictionary<string, Stock> _stocks = new ConcurrentDictionary<string, Stock>(); private readonly object _updateStockPricesLock = new object(); //stock can go up or down by a percentage of this factor on each change
private readonly double _rangePercent = .002; private readonly TimeSpan _updateInterval = TimeSpan.FromMilliseconds(250);
private readonly Random _updateOrNotRandom = new Random(); private readonly Timer _timer;
private volatile bool _updatingStockPrices = false; private StockTicker(IHubConnectionContext<dynamic> clients)
{
Clients = clients; _stocks.Clear();
var stocks = new List<Stock>
{
new Stock { Symbol = "MSFT", Price = 30.31m },
new Stock { Symbol = "APPL", Price = 578.18m },
new Stock { Symbol = "GOOG", Price = 570.30m }
};
stocks.ForEach(stock => _stocks.TryAdd(stock.Symbol, stock)); _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval); } public static StockTicker Instance
{
get
{
return _instance.Value;
}
} private IHubConnectionContext<dynamic> Clients
{
get;
set;
} public IEnumerable<Stock> GetAllStocks()
{
return _stocks.Values;
} private void UpdateStockPrices(object state)
{
lock (_updateStockPricesLock)
{
if (!_updatingStockPrices)
{
_updatingStockPrices = true; foreach (var stock in _stocks.Values)
{
if (TryUpdateStockPrice(stock))
{
BroadcastStockPrice(stock);
}
} _updatingStockPrices = false;
}
}
} private bool TryUpdateStockPrice(Stock stock)
{
var r = _updateOrNotRandom.NextDouble();
if (r > .1)
{
return false;
}
var random = new Random((int)Math.Floor(stock.Price));
var percentChange = random.NextDouble() * _rangePercent;
var pos = random.NextDouble() > .51;
var change = Math.Round(stock.Price * (decimal)percentChange, 2);
change = pos ? change : -change; stock.Price += change;
return true;
} private void BroadcastStockPrice(Stock stock)
{
Clients.All.updateStockPrice(stock);
} }
由于多个线程通过要访问StockTicker,所以StockTicker必须是线程安全的。
好了,现在完成了基本的配置,我们创建一个名为index.html的文件并把它设置为启动项。index.html的代码如下
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>ASP.NET SignalR Stock Ticker</title>
<style>
body {
font-family: 'Segoe UI', Arial, Helvetica, sans-serif;
font-size: 16px;
} #stockTable table {
border-collapse: collapse;
} #stockTable table th, #stockTable table td {
padding: 2px 6px;
} #stockTable table td {
text-align: right;
} #stockTable .loading td {
text-align: left;
}
</style>
</head>
<body>
<h1>ASP.NET SignalR Stock Ticker Sample</h1> <h2>Live Stock Table</h2>
<div id="stockTable">
<table border="1">
<thead>
<tr><th>Symbol</th><th>Price</th><th>Open</th><th>Change</th><th>%</th></tr>
</thead>
<tbody>
<tr class="loading"><td colspan="5">loading...</td></tr>
</tbody>
</table>
</div>
<script src="Scripts/jquery-1.6.4.min.js"></script>
<script src="Scripts/jquery.signalR-2.2.0.js"></script>
<script src="/signalr/hubs"></script>
<script src="StockTicker.js"></script>
</body>
</html>
在项目新建一个名为StockTicker.js的文件,并用以下代码替换
if (!String.prototype.supplant) {
String.prototype.supplant = function (o) {
return this.replace(/{([^{}]*)}/g,
function (a, b) {
var r = o[b];
return typeof r === 'string' || typeof r === 'number' ? r : a;
}
);
};
}
$(function () {
var ticker = $.connection.stockTickerMini,
up = '▲',
down = '▼',
$stockTable = $('#stockTable'),
$stockTableBody = $stockTable.find('tbody'),
rowTemplate = '<tr data-symbol="{Symbol}"><td>{Symbol}</td><td>{Price}</td><td>{DayOpen}</td><td>{Direction} {Change}</td><td>{PercentChange}</td></tr>';
function formatStock(stock) {
return $.extend(stock, {
Price: stock.Price.toFixed(2),
PercentChange: (stock.PercentChange * 100).toFixed(2) + '%',
Direction: stock.Change === 0 ? '' : stock.Change >= 0 ? up : down
});
}
function init() {
//建立连接成功后走的代码
ticker.server.getAllStocks().done(function (stocks) {
$stockTableBody.empty();
$.each(stocks, function () {
var stock = formatStock(this);
$stockTableBody.append(rowTemplate.supplant(stock));
});
});
}
ticker.client.updateStockPrice = function (stock) {
var displayStock = formatStock(stock),
$row = $(rowTemplate.supplant(displayStock));
$stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']')
.replaceWith($row);
}
$.connection.hub.start().done(init);
});
$.connection是指SignalR的代理,StockTickerMini是StockTickerHub类HubName所设置的别名。
在所有的变量和函数定义后,会在最后一行启动SignalR连接,并进行初始化。运行起来即可看到效果

请大家注意一下StockTicker类中的BroadcastStockPrice方法,这个方法最终会获取当前所有的连接用户,并触发updateStockPrice方法。在前两章的时候我们都是由客户端主动调用Hub类的某一个方法,然后在由方法内部进行触发前台JS代码。在本章中,我们定义了一个定时器用来定时更新数据,每当数据发生修改时就会主动触发updateStockPrice,这也是本章与前两章不同的地方。
SignalR系列教程:服务器广播与主动数据推送的更多相关文章
- ASP.NET SignalR 系列(八)之跨域推送
前面几章讲的都是同域下的推送和订阅.这种讲讲如何跨域 对于SignalR来说,默认是不允许跨域的,因为安全问题.虽如此,但同时提供了跨域方案. 两种跨域方式: 1:JSONP2:CORS JSONP的 ...
- ASP.NET SignalR 系列(四)之指定对象推送
在上一章讲到了广播推送,即所有订阅的用户都能收到,这种适合于信息广播. 接下来介绍如何给指定的对象推送 在讲这个之前先说明一下连接创建的基础知识 1.每个页面与服务端创建连接并启动时,这时服务端会产生 ...
- ASP.NET SignalR 系列(五)之群组推送
在上一章介绍了 一对一推送的方式,这章重点介绍下群组推送和多人推送 群组主要就是用到了方法:Groups.Add(Context.ConnectionId, groupName); 将不同的连接id加 ...
- 史上最全面的SignalR系列教程-2、SignalR 实现推送功能-永久连接类实现方式
1.概述 通过上篇史上最全面的SignalR系列教程-1.认识SignalR文章的介绍,我们对SignalR技术已经有了一个全面的了解.本篇开始就通过SignalR的典型应用的实现方式做介绍,例子虽然 ...
- 史上最全面的SignalR系列教程-3、SignalR 实现推送功能-集线器类实现方式
1.概述 通过前两篇 史上最全面的SignalR系列教程-1.认识SignalR 史上最全面的SignalR系列教程-2.SignalR 实现推送功能-永久连接类实现方式 文章对SignalR的介绍, ...
- 史上最全面的SignalR系列教程-6、SignalR 实现聊天室
1.概述 通过前面几篇文章对SignalR的详细介绍.我们知道Asp.net SignalR是微软为实现实时通信的一个类库.一般情况下,SignalR会使用JavaScript的长轮询(long po ...
- 史上最全面的SignalR系列教程-4、SignalR 自托管全解(使用Self-Host)-附各终端详细实例
1.概述 通过前面几篇文章 史上最全面的SignalR系列教程-1.认识SignalR 史上最全面的SignalR系列教程-2.SignalR 实现推送功能-永久连接类实现方式 史上最全面的Signa ...
- 史上最全面的SignalR系列教程-目录汇总
1.引言 最遗憾的不是把理想丢在路上,而是理想从未上路. 每一个将想法变成现实的人,都值得称赞和学习. 致正在奔跑的您! 2.SignalR介绍 SignalR实现服务器与客户端的实时通信 ,她是一个 ...
- SignalR系列教程:SignalR快速入门
---恢复内容开始--- 本篇是SignalR系列教程的第一篇,本篇内容介绍了如何创建SignalR应用,如何利用SignalR搭建简易的聊天室等,本篇内容参考自:http://www.asp.net ...
随机推荐
- 一个简单二叉树的C++实现(一)
很久没有接触二叉树了,写这个当作练手,接下来会比较详细地实现二叉树的各个功能及应用. /* * BinaryTree.cpp * Author: Qiang Xiao * Time: 2015-07- ...
- BZOJ 1599: [Usaco2008 Oct]笨重的石子( 枚举 )
直接枚举 ------------------------------------------------------------------------------- #include<cst ...
- CentOS6.5安装Python2.7和Pip
注:文中所写的安装过程均在CentOS6.5 x86下通过测试,安装的Python版本为2.7.12,Pip版本为8.1.2 主要参考博文:http://bicofino.io/2014/01/16/ ...
- nginx启动
查看nginx的进程 ps -ef | grep nginx 重启nginx的3种办法1.service nginx restart2.改了配置文件让其生效办法 nginx -s reload3.到n ...
- hdu 2814 快速求欧拉函数
/** 大意: 求[a,b] 之间 phi(a) + phi(a+1)...+ phi(b): 思路: 快速求欧拉函数 **/ #include <iostream> #include & ...
- 升级到iis7 的web.config配置
经典模式或集成模式都识别system.webServers节点 aspnet的isapi分32位和64位 不存在时会报404或403
- 3,C语言文件读写
这两天看到一个关于文件读写的题目,索性就把相关内容总结下. C语言文件读写,无非是几个读写函数的应用,fopen(),fread(),fwrite()等,下面简单介绍下. 一.fopen() 函数原型 ...
- 转:requirejs打包压缩r.js使用示例
为了应对日益复杂,大规模的JavaScript开发.我们化整为零,化繁为简.将复杂的逻辑划分一个个小单元,各个击破.这时一个项目可能会有几十个甚至上百个JS文件,每个文件为一个模块单元.如果上线时都是 ...
- border-radius.htc为ie6-8实现圆角
~~圆角是比较常用的css3属性,但是ie6-8并不支持圆角,可用border-radius.htc html组件实现圆角, border-radius.htc内部应用vml进行了重绘 border ...
- KBMMW SampleService/SampleClient方式传输数据集
马上周末了,趁着下午这会儿回顾一下这几天对旧项目的升级过程,一些重要但不常用的东西记录下来是很有必要的.其中一个项目中对KBMMW的远程数据通讯方式做了改进,利用SampleService/Sampl ...