英文渣水平,大伙凑合着看吧……
这是微软官方SignalR 2.0教程Getting Started with asp.net SignalR 2.0系列的翻译,这里是第八篇:SignalR的服务器广播
原文:Tutorial: Server Broadcast with SignalR 2.0
VS可以通过 Microsoft.AspNet.SignalR.SampleNuGet包来安装一个简单的模拟股票行情应用。在本教程的第一部分,您将从头开始创建一个应用程序的简化版本。在本教程的剩余部分,您将安装NuGet包,审阅Sample中的一些附加功能。
本模拟股票行情应用代表了实时应用中的"推",或称之为广播,即我们将消息通知传播给所有已连接客户端。
第一步,您将要创建该应用程序的显示表格用于显示数据。
接下来,服务器会随机更新股票价格,并且将新数据推送至所有连接的客户端以更新表格。在浏览器中的表格上,价格及百分比列中的数字都会随着服务器推送数据而自动更新。如果你打开更多的浏览器,它们都会显示相同的数据及自动更新。
注意:如果您你不想自己手动来构建这一应用程序,你可以再一个新的空ASP.NET WEB应用项目中安装Simple包,通过阅读这些步骤来获取代码的解释。本教程的第一部分涵盖了Sample的子集,第二部分解释了包中的一些附加功能。
1.新建一个新的ASP.NET应用程序,命名为SignalR.StockTicker并创建。
2.选择空项目并确定。
在本节中,我们来编写服务器端代码。
首先我们来创建一个Stock模型类,用来存储和传输股票信息。
1.新建一个类,命名为Stock.cs,然后输入以下代码:
1 using System; 2 3 namespace SignalR.StockTicker 4 { 5 public class Stock 6 { 7 PRivate decimal _price; 8 9 public string Symbol { get; set; }10 11 public decimal Price12 {13 get14 {15 return _price;16 }17 set18 {19 if (_price == value)20 {21 return;22 }23 24 _price = value;25 26 if (DayOpen == 0)27 {28 DayOpen = _price;29 }30 }31 }32 33 public decimal DayOpen { get; private set; }34 35 public decimal Change36 {37 get38 {39 return Price - DayOpen;40 }41 }42 43 public double PercentChange44 {45 get46 {47 return (double)Math.Round(Change / Price, 4);48 }49 }50 }51 }
您设置了两个属性:股票代码及价格。其他的属性则依赖于你如何及何时设置股票价格。当您首次设定价格时,价格将被存储在DayOpen中。之后随着股票价格的改变,Change和PercentChange会自动计算DayOpen及价格之间的差额并输出结果。
您将使用SignalR集线器类的API来处理服务器到客户端的交互。StockTickerHub衍生自SignalR集线器基类,用来处理接收客户端的连接和调用方法。你还需要维护存储的数据,建立一个独立于客户端连接的Timer对象,来触发价格更新。你不能将这些功能放在集线器中,因为每个针对集线器的操作,比如从客户端到服务器端的连接与调用都会建立一个集线器的新实例,每个集线器的实例生存期是短暂的。因此,保存数据,价格,广播等更新机制需要放在一个单独的类中。在此项目中我们将其命名为StockTicker。
你只需要一个StockTicker类的实例。所以你需要使用设计模式中的单例模式,从每个StockTickerHub的类中添加对StockTicker单一实例的引用。由于StockTicker类包含股票数据并触发更新,所以它必须能够广播到每个客户端。但StockTicker本身并不是一个集线器类,所以StockTicker类必须得到一个SignalR集线器连接上下文对象的引用,之后就可以使用这个上下文对象来将数据广播给客户端。
1.添加一个新的SignalR集线器类,命名为StockTickerHub并使用以下的代码替换其内容:
1 using System.Collections.Generic; 2 using Microsoft.AspNet.SignalR; 3 using Microsoft.AspNet.SignalR.Hubs; 4 5 namespace SignalR.StockTicker 6 { 7 [HubName("stockTickerMini")] 8 public class StockTickerHub : Hub 9 {10 private readonly StockTicker _stockTicker;11 12 public StockTickerHub() : this(StockTicker.Instance) { }13 14 public StockTickerHub(StockTicker stockTicker)15 {16 _stockTicker = stockTicker;17 }18 19 public IEnumerable<Stock> GetAllStocks()20 {21 return _stockTicker.GetAllStocks();22 }23 }24 }
此集线器类用来定义用于客户端调用的服务器方法。我们定义了一个GetAllStocks方法,当一个客户端首次连接至服务器时,它会调用此方法来获取所有股票的清单及当期价格。该方法可以同步执行并返回IEnumerable<Sotck>,因为这些数据是从内存中返回的。如果该方法需要做一些涉及等待的额外处理任务,比如数据库查询或调用Web服务来获取数据,您将指定Task<IEnumerable<Stock>>作为返回值已启用异步处理。关于异步处理的更多信息,请参阅:ASP.NET SignalR Hubs API Guide - Server - When to execute asynchronously。
HubName特性定义了客户端的JS代码使用何种名称来调用集线器。如果你不使用这个特性,默认将通过采用使用Camel规范的类名来调用。在本例中,我们使用stockTickerHun。
稍后我们将创建StockTicker类,如您所见,我们在这里使用了单例模式。使用一个静态实例属性来创建这个类的单一实例。StockTicker的单例将一直保留在内存中,不管有多少客户端连接或断开连接。并且使用该实例中包含的GetAllStocks方法返回股票信息。
2.添加一个新类,命名为StockTicker.cs,并使用以下代码替换内容:
1 using System; 2 using System.Collections.Concurrent; 3 using System.Collections.Generic; 4 using System.Threading; 5 using Microsoft.AspNet.SignalR; 6 using Microsoft.AspNet.SignalR.Hubs; 7 8 9 namespace SignalR.StockTicker 10 { 11 public class StockTicker 12 { 13 // Singleton instance 14 private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients)); 15 16 private readonly ConcurrentDictionary<string, Stock> _stocks = new ConcurrentDictionary<string, Stock>(); 17 18 private readonly object _updateStockPricesLock = new object(); 19 20 //stock can go up or down by a percentage of this factor on each change 21 private readonly double _rangePercent = .002; 22 23 private readonly TimeSpan _updateInterval = TimeSpan.FromMilliseconds(250); 24 private readonly Random _updateOrNotRandom = new Random(); 25 26 private readonly Timer _timer; 27 private volatile bool _updatingStockPrices = false; 28 29 private StockTicker(IHubConnectionContext clients) 30 { 31 Clients = clients; 32 33 _stocks.Clear(); 34 var stocks = new List<Stock> 35 { 36 new Stock { Symbol = "MSFT", Price = 30.31m }, 37 new Stock { Symbol = "APPL", Price = 578.18m }, 38 new Stock { Symbol = "GOOG", Price = 570.30m } 39 }; 40 stocks.ForEach(stock => _stocks.TryAdd(stock.Symbol, stock)); 41 42 _timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval); 43 44 } 45 46 public static StockTicker Instance 47 { 48 get 49 { 50 return _instance.Value; 51 } 52 } 53 54 private IHubConnectionContext Clients 55 { 56 get; 57 set; 58 } 59 60 public IEnumerable<Stock> GetAllStocks() 61 { 62 return _stocks.Values; 63 } 64 65 private void UpdateStockPrices(object state) 66 { 67 lock (_updateStockPricesLock) 68 { 69 if (!_updatingStockPrices) 70 { 71 _updatingStockPrices = true; 72 73 foreach (var stock in _stocks.Values) 74 { 75 if (TryUpdateStockPrice(stock)) 76 { 77 BroadcastStockPrice(stock); 78 } 79 } 80 81 _updatingStockPrices = false; 82 } 83 } 84 } 85 86 private bool TryUpdateStockPrice(Stock stock) 87 { 88 // Randomly choose whether to update this stock or not 89 var r = _updateOrNotRandom.NextDouble(); 90 if (r > .1) 91 { 92 return false; 93 } 94 95 // Update the stock price by a random factor of the range percent 96 var random = new Random((int)Math.Floor(stock.Price)); 97 var percentChange = random.NextDouble() * _rangePercent; 98 var pos = random.NextDouble() > .51; 99 var change = Math.Round(stock.Price * (decimal)percentChange, 2);100 change = pos ? change : -change;101 102 stock.Price += change;103 return true;104 }105 106 private void BroadcastStockPrice(Stock stock)107 {108 Clients.All.updateStockPrice(stock);109 }110 111 }112 }
由于运行时会有多个线程对StockTicker的同一个实例进行操作,StockTicker类必须是线程安全的。
下面的代码用于在静态_instance字段中初始化一个StockTicker的实例。这是该类的唯一一个实例,因为构造函数已经被标记为私有的。_instance中的延迟初
新闻热点
疑难解答