英文渣水平,大伙湊合著看吧……
這是微軟官方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中的延遲初
新聞熱點
疑難解答