Руководство. Широковещательная трансляция сервера с помощью SignalR 2
Предупреждение
Эта документация не подходит для последней версии SignalR. Ознакомьтесь с ASP.NET Core SignalR.
В этом руководстве показано, как создать веб-приложение, которое использует ASP.NET SignalR 2 для предоставления функций широковещательного вещания сервера. Широковещательная трансляция сервера означает, что сервер запускает обмен данными, отправляемые клиентам.
Приложение, которое вы создадите в этом руководстве, имитирует тиккер акций, типичный сценарий для широковещательных функций сервера. Периодически сервер случайным образом обновляет цены на акции и передает обновления всем подключенным клиентам. В браузере числа и символы в столбцах Изменения и % динамически изменяются в ответ на уведомления с сервера. Если вы открываете дополнительные браузеры с тем же URL-адресом, они отображают одни и те же данные и те же изменения одновременно.
Изучив это руководство, вы:
- Создание проекта
- Настройка кода сервера
- Изучение кода сервера
- Настройка клиентского кода
- Изучение клиентского кода
- Тестирование приложения
- Включение ведения журналов
Важно!
Если вы не хотите выполнять шаги по созданию приложения, можно установить пакет SignalR.Sample в новом проекте пустого веб-приложения ASP.NET. Если вы устанавливаете пакет NuGet без выполнения действий, описанных в этом руководстве, необходимо выполнить инструкции в файлеreadme.txt . Чтобы запустить пакет, необходимо добавить класс запуска OWIN, который вызывает ConfigureSignalR
метод в установленном пакете. Если не добавить класс запуска OWIN, появится сообщение об ошибке. См. раздел Установка примера StockTicker этой статьи.
Предварительные требования
- Visual Studio 2017 с рабочей нагрузкой ASP.NET и веб-разработка.
Создание проекта
В этом разделе показано, как использовать Visual Studio 2017 для создания пустого веб-приложения ASP.NET.
В Visual Studio создайте веб-приложение ASP.NET.
В окне Новое веб-приложение ASP.NET — SignalR.StockTicker оставьте флажок Пустой и нажмите кнопку ОК.
Настройка кода сервера
В этом разделе вы настроите код, который выполняется на сервере.
Создание класса Stock
Для начала вы создадите класс модели Stock , который будет использоваться для хранения и передачи сведений о акции.
В Обозреватель решений щелкните правой кнопкой мыши проект и выберите Добавить>класс.
Назовите класс Stock и добавьте его в проект.
Замените код в файле Stock.cs следующим кодом:
using System; namespace SignalR.StockTicker { 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); } } } }
Два свойства, которые вы задали при создании акций
Symbol
: (например, MSFT для Майкрософт) иPrice
. Другие свойства зависят от того, как и когда вы задаетеPrice
. При первом установкеPrice
значение распространяется наDayOpen
. После этого при установкеPrice
приложение вычисляет значения свойств иPercentChange
на основе разницыChange
междуPrice
иDayOpen
.
Создание классов StockTickerHub и StockTicker
Вы будете использовать API-интерфейс Концентратора SignalR для обработки взаимодействия между сервером и клиентом. Класс StockTickerHub
, производный от класса SignalR, Hub
будет обрабатывать получение подключений и вызовов методов от клиентов. Кроме того, необходимо хранить данные о запасах и запускать Timer
объект . Объект Timer
будет периодически запускать обновления цен независимо от клиентских подключений. Эти функции нельзя поместить в Hub
класс, так как центры являются временными. Приложение создает экземпляр класса для каждой Hub
задачи в концентраторе, например для подключений и вызовов клиента к серверу. Таким образом, механизм, который хранит данные о акциях, обновляет цены и транслирует обновления цен, должен работать в отдельном классе. Вы присвойте классу StockTicker
имя .
На сервере StockTicker
должен выполняться только один экземпляр класса , поэтому необходимо настроить ссылку из каждого StockTickerHub
экземпляра на отдельный StockTicker
экземпляр. Класс StockTicker
должен быть широковещательным для клиентов, так как он содержит данные о запасах и активирует обновления, но StockTicker
не является классом Hub
. Класс StockTicker
должен получить ссылку на объект контекста подключения Концентратор SignalR. Затем он может использовать объект контекста подключения SignalR для трансляции клиентам.
Создание Файла StockTickerHub.cs
В Обозреватель решений щелкните проект правой кнопкой мыши и выберите добавить>новый элемент.
В разделе Добавление нового элемента — SignalR.StockTicker выберите Установленные>Visual C#>Web>SignalR, а затем — Класс концентратора SignalR (версия 2).
Назовите класс StockTickerHub и добавьте его в проект.
На этом шаге создается файл класса StockTickerHub.cs . Одновременно он добавляет в проект набор файлов скриптов и ссылок на сборки, которые поддерживают SignalR.
Замените код в файле StockTickerHub.cs следующим кодом:
using System.Collections.Generic; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; namespace SignalR.StockTicker { [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(); } } }
Сохраните файл.
Приложение использует класс Hub для определения методов, которые клиенты могут вызывать на сервере. Вы определяете один метод: GetAllStocks()
. Когда клиент изначально подключается к серверу, он вызывает этот метод, чтобы получить список всех акций с текущими ценами. Метод может выполняться синхронно и возвращать IEnumerable<Stock>
, так как он возвращает данные из памяти.
Если метод должен был получить данные, выполнив какие-либо действия, требующие ожидания, например подстановку базы данных или вызов веб-службы, необходимо указать Task<IEnumerable<Stock>>
в качестве возвращаемого значения, чтобы включить асинхронную обработку. Дополнительные сведения см . в ASP.NET Руководстве по API Центров SignalR — сервер — когда выполнять асинхронно.
Атрибут HubName
указывает, как приложение будет ссылаться на концентратор в коде JavaScript на клиенте. Имя по умолчанию на клиенте, если вы не используете этот атрибут, — это версия camelCase имени класса, которая в данном случае будет иметь значение stockTickerHub
.
Как вы увидите позже при создании StockTicker
класса, приложение создает одноэлементный экземпляр этого класса в своем статическом Instance
свойстве. Этот одноэлементный StockTicker
экземпляр находится в памяти независимо от того, сколько клиентов подключается или отключается. Этот экземпляр используется методом GetAllStocks()
для возврата текущих сведений о запасах.
Создание Файла StockTicker.cs
В Обозреватель решений щелкните правой кнопкой мыши проект и выберите Добавить>класс.
Назовите класс StockTicker и добавьте его в проект.
Замените код в файле StockTicker.cs следующим кодом:
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using Microsoft.AspNet.SignalR; using Microsoft.AspNet.SignalR.Hubs; namespace SignalR.StockTicker { public class StockTicker { // Singleton instance private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients)); 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) { // Randomly choose whether to update this stock or not var r = _updateOrNotRandom.NextDouble(); if (r > .1) { return false; } // Update the stock price by a random factor of the range percent 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 должен быть потокобезопасным.
Изучение кода сервера
Если вы изучите код сервера, это поможет вам понять, как работает приложение.
Хранение одноэлементного экземпляра в статическом поле
Код инициализирует статическое _instance
поле, которое поддерживает Instance
свойство экземпляром класса . Поскольку конструктор является закрытым, это единственный экземпляр класса, который может создать приложение. Приложение использует отложенную инициализацию_instance
для поля. Это не из соображений производительности. Это необходимо, чтобы убедиться, что создание экземпляра является потокобезопасно.
private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));
public static StockTicker Instance
{
get
{
return _instance.Value;
}
}
Каждый раз, когда клиент подключается к серверу, новый экземпляр класса StockTickerHub, выполняющийся в отдельном потоке, получает одноэлементный экземпляр StockTicker из StockTicker.Instance
статического свойства, как вы видели ранее в StockTickerHub
классе .
Хранение данных о запасах в ConcurrentDictionary
Конструктор инициализирует коллекцию _stocks
с помощью некоторых выборочных данных о запасах и GetAllStocks
возвращает запасы. Как вы видели ранее, эта коллекция запасов возвращается методом StockTickerHub.GetAllStocks
, который является серверным методом в Hub
классе, который клиенты могут вызывать.
private readonly ConcurrentDictionary<string, Stock> _stocks = new ConcurrentDictionary<string, Stock>();
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 IEnumerable<Stock> GetAllStocks()
{
return _stocks.Values;
}
Коллекция запасов определяется как тип ConcurrentDictionary для потокобезопасности. В качестве альтернативы можно использовать объект Dictionary и явно заблокировать словарь при внесении изменений в него.
Для этого примера приложения можно хранить данные приложения в памяти и терять их, когда приложение удаляет StockTicker
экземпляр. В реальном приложении вы будете работать с серверным хранилищем данных, например с базой данных.
Периодическое обновление цен на акции
Конструктор запускает Timer
объект , который периодически вызывает методы, которые обновляют цены на акции случайным образом.
_timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
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)
{
// Randomly choose whether to update this stock or not
var r = _updateOrNotRandom.NextDouble();
if (r > .1)
{
return false;
}
// Update the stock price by a random factor of the range percent
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;
}
Timer
вызывает UpdateStockPrices
, который передает значение NULL в параметре state. Перед обновлением цен приложение блокирует _updateStockPricesLock
объект . Код проверяет, обновляет ли другой поток цены, а затем вызывает TryUpdateStockPrice
для каждой акции в списке. Метод TryUpdateStockPrice
решает, следует ли изменять цену акций и сколько ее изменить. Если цена акций меняется, приложение вызывает трансляцию BroadcastStockPrice
изменения цен акций всем подключенным клиентам.
Флаг _updatingStockPrices
, назначенный переменным , чтобы убедиться, что он является потокобезопасным.
private volatile bool _updatingStockPrices = false;
В реальном приложении метод вызывает веб-службу для TryUpdateStockPrice
поиска цены. В этом коде приложение использует генератор случайных чисел для случайных изменений.
Получение контекста SignalR для трансляции класса StockTicker клиентам
Так как изменения цен происходят здесь в объекте StockTicker
, именно объект должен вызывать updateStockPrice
метод для всех подключенных клиентов. Hub
В классе есть API для вызова клиентских методов, но StockTicker
он не является производным Hub
от класса и не имеет ссылки на какой-либо Hub
объект. Для широковещательной передачи подключенным StockTicker
клиентам класс должен получить экземпляр контекста SignalR для класса и использовать его для StockTickerHub
вызова методов на клиентах.
Код получает ссылку на контекст SignalR при создании экземпляра одноэлементного класса, передает ее конструктору, а конструктор помещает ее в Clients
свойство .
Существует две причины, по которым требуется получить контекст только один раз: получение контекста является дорогостоящей задачей, а однократное получение гарантирует, что приложение сохраняет нужный порядок сообщений, отправляемых клиентам.
private readonly static Lazy<StockTicker> _instance =
new Lazy<StockTicker>(() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>().Clients));
private StockTicker(IHubConnectionContext<dynamic> clients)
{
Clients = clients;
// Remainder of constructor ...
}
private IHubConnectionContext<dynamic> Clients
{
get;
set;
}
private void BroadcastStockPrice(Stock stock)
{
Clients.All.updateStockPrice(stock);
}
Clients
Получение свойства контекста и его помещение в StockTickerClient
свойство позволяет написать код для вызова клиентских методов, который выглядит так же, как в Hub
классе. Например, для трансляции на всех клиентах можно написать Clients.All.updateStockPrice(stock)
.
Метод updateStockPrice
, который вы вызываете, BroadcastStockPrice
еще не существует. Вы добавите его позже при написании кода, который выполняется на клиенте. Здесь можно ссылаться, updateStockPrice
так как Clients.All
является динамическим. Это означает, что приложение будет оценивать выражение во время выполнения. При выполнении этого вызова метода SignalR отправит клиенту имя метода и значение параметра, а если у клиента есть метод с именем updateStockPrice
, приложение вызовет этот метод и передаст ему значение параметра.
Clients.All
означает отправку всем клиентам. SignalR предоставляет другие параметры для указания клиентов или групп клиентов для отправки. Дополнительные сведения см. в разделе HubConnectionContext.
Регистрация маршрута SignalR
Сервер должен знать, какой URL-адрес следует перехватить и направить в SignalR. Для этого добавьте класс запуска OWIN:
В Обозреватель решений щелкните правой кнопкой мыши проект и выберите Добавить>новый элемент.
В разделе Добавление нового элемента — SignalR.StockTicker выберите Установленные>Visual C#>Web , а затем — Класс запуска OWIN.
Назовите класс Startup и нажмите кнопку ОК.
Замените код по умолчанию в файле Startup.cs следующим кодом:
using System; using System.Threading.Tasks; using Microsoft.Owin; using Owin; [assembly: OwinStartup(typeof(SignalR.StockTicker.Startup))] namespace SignalR.StockTicker { public class Startup { public void Configuration(IAppBuilder app) { // Any connection or hub wire up and configuration should go here app.MapSignalR(); } } }
Теперь вы завершили настройку кода сервера. В следующем разделе вы настроите клиент.
Настройка кода клиента
В этом разделе вы настроите код, который выполняется на клиенте.
Создание HTML-страницы и файла JavaScript
На HTML-странице отобразятся данные, а файл JavaScript упорядочит данные.
Создание StockTicker.html
Сначала добавьте HTML-клиент.
В Обозреватель решений щелкните правой кнопкой мыши проект и выберите Добавить>HTML-страницу.
Назовите файл StockTicker и нажмите кнопку ОК.
Замените код по умолчанию в файле StockTicker.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 references. --> <!--Reference the jQuery library. --> <script src="/Scripts/jquery-1.10.2.min.js" ></script> <!--Reference the SignalR library. --> <script src="/Scripts/jquery.signalR-2.1.0.js"></script> <!--Reference the autogenerated SignalR hub script. --> <script src="/signalr/hubs"></script> <!--Reference the StockTicker script. --> <script src="StockTicker.js"></script> </body> </html>
HTML-код создает таблицу с пятью столбцами, строку заголовка и строку данных с одной ячейкой, охватывающей все пять столбцов. В строке данных отображается сообщение "загрузка..." на мгновение при запуске приложения. Код JavaScript удалит эту строку и добавит на ее место строки со стандартными данными, полученными с сервера.
Теги скрипта указывают:
Файл скрипта jQuery.
Файл основного скрипта SignalR.
Файл скрипта прокси-серверов SignalR.
Файл скрипта StockTicker, который вы создадите позже.
Приложение динамически создает файл скрипта прокси-серверов SignalR. Он задает URL-адрес "/signalr/hubs" и определяет методы прокси-сервера для методов в классе концентратора, в данном случае для
StockTickerHub.GetAllStocks
. При желании этот файл JavaScript можно создать вручную с помощью служебных программ SignalR. Не забудьте отключить динамическое создание файла в вызовеMapHubs
метода.В Обозреватель решений разверните узел Скрипты.
Библиотеки скриптов для jQuery и SignalR видны в проекте.
Важно!
Диспетчер пакетов установит более позднюю версию скриптов SignalR.
Обновите ссылки на скрипты в блоке кода в соответствии с версиями файлов скриптов в проекте.
В Обозреватель решений щелкните правой кнопкой мыши StockTicker.htmlи выберите пункт Задать как начальную страницу.
Создание StockTicker.js
Теперь создайте файл JavaScript.
В Обозреватель решений щелкните правой кнопкой мыши проект и выберите Добавить>файл JavaScript.
Назовите файл StockTicker и нажмите кнопку ОК.
Добавьте следующий код в файлStockTicker.js :
// A simple templating method for replacing placeholders enclosed in curly braces. 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, // the generated client-side hub proxy 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)); }); }); } // Add a client-side hub method that the server will call ticker.client.updateStockPrice = function (stock) { var displayStock = formatStock(stock), $row = $(rowTemplate.supplant(displayStock)); $stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']') .replaceWith($row); } // Start the connection $.connection.hub.start().done(init); });
Изучение клиентского кода
Изучение клиентского кода поможет узнать, как клиентский код взаимодействует с серверным кодом, чтобы обеспечить работу приложения.
Запуск подключения
$.connection
ссылается на прокси-серверы SignalR. Код получает ссылку на прокси-сервер для StockTickerHub
класса и помещает его в переменную ticker
. Имя прокси-сервера — это имя, заданное атрибутом HubName
:
var ticker = $.connection.stockTickerMini
[HubName("stockTickerMini")]
public class StockTickerHub : Hub
После определения всех переменных и функций последняя строка кода в файле инициализирует подключение SignalR путем вызова функции SignalR start
. Функция start
выполняется асинхронно и возвращает объект jQuery Deferred. Можно вызвать функцию done, чтобы указать функцию, вызываемую по завершении асинхронного действия приложением.
$.connection.hub.start().done(init);
Получение всех запасов
Функция init
вызывает функцию getAllStocks
на сервере и использует сведения, возвращаемые сервером, для обновления биржевой таблицы. Обратите внимание, что по умолчанию необходимо использовать camelCasing на клиенте, даже если на сервере используется имя метода pascal. Правило camelCasing применяется только к методам, а не к объектам. Например, вы ссылаетесь на stock.Symbol
и stock.Price
, а не stock.symbol
на или stock.price
.
function init() {
ticker.server.getAllStocks().done(function (stocks) {
$stockTableBody.empty();
$.each(stocks, function () {
var stock = formatStock(this);
$stockTableBody.append(rowTemplate.supplant(stock));
});
});
}
public IEnumerable<Stock> GetAllStocks()
{
return _stockTicker.GetAllStocks();
}
В методе init
приложение создает HTML-код для строки таблицы для каждого стандартного объекта, полученного stock
с сервера, путем вызова formatStock
для форматирования свойств объекта, а затем путем замены supplant
заполнителей в rowTemplate
переменной значениями stock
свойств объекта. Полученный HTML-код добавляется в таблицу stock.
Примечание
Вызов выполняется init
путем передачи его в качестве callback
функции, которая выполняется после завершения асинхронной start
функции. Если вы вызвали init
как отдельный оператор JavaScript после вызова start
, функция завершится ошибкой, так как она будет выполняться немедленно, не дожидаясь завершения подключения функции start. В этом случае функция попытается вызвать функцию getAllStocks
до того, init
как приложение установит подключение к серверу.
Получение обновленных цен на акции
Когда сервер изменяет цену акций, он вызывает на подключенных updateStockPrice
клиентах. Приложение добавляет функцию в свойство stockTicker
клиента прокси-сервера, чтобы сделать ее доступной для вызовов с сервера.
ticker.client.updateStockPrice = function (stock) {
var displayStock = formatStock(stock),
$row = $(rowTemplate.supplant(displayStock));
$stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']')
.replaceWith($row);
}
Функция updateStockPrice
форматирует стандартный объект, полученный от сервера, в строку таблицы так же, как и init
в функции. Вместо добавления строки в таблицу она находит текущую строку акций в таблице и заменяет ее новой.
Тестирование приложения
Вы можете протестировать приложение, чтобы убедиться, что оно работает. Вы увидите, что во всех окнах браузера отображается таблица акций с колебаниями цен на акции.
На панели инструментов включите отладку скриптов и нажмите кнопку воспроизведения, чтобы запустить приложение в режиме отладки.
Откроется окно браузера с таблицей Live Stock. В таблице акций изначально отображается "загрузка..." затем через некоторое время приложение отображает исходные данные акций, а затем цены на акции начинают меняться.
Скопируйте URL-адрес из браузера, откройте два других браузера и вставьте URL-адреса в адресные строки.
Начальное отображение акций совпадает с первым браузером, и изменения происходят одновременно.
Закройте все браузеры, откройте новый браузер и перейдите по тому же URL-адресу.
Одноэлементный объект StockTicker продолжал выполняться на сервере. Таблица live stock показывает, что запасы продолжают меняться. Начальная таблица с нулевыми цифрами изменений не отображается.
Закройте браузер.
Включение ведения журналов
SignalR имеет встроенную функцию ведения журнала, которую можно включить на клиенте для устранения неполадок. В этом разделе описано, как включить ведение журнала и просмотреть примеры, показывающие, как журналы сообщают вам, какой из следующих методов транспорта использует SignalR:
WebSocket, поддерживаемые IIS 8 и текущими браузерами.
События, отправляемые сервером, поддерживаемые браузерами, не Обозреватель Интернета.
Рамка Forever, поддерживаемая интернет-Обозреватель.
Длинный опрос Ajax, поддерживаемый всеми браузерами.
Для любого подключения SignalR выбирает оптимальный метод транспорта, который поддерживается как сервером, так и клиентом.
Откройте StockTicker.js.
Добавьте выделенную строку кода, чтобы включить ведение журнала непосредственно перед кодом, который инициализирует подключение в конце файла:
// Start the connection $.connection.hub.logging = true; $.connection.hub.start().done(init);
Нажмите клавишу F5 , чтобы запустить проект.
Откройте окно средств разработчика в браузере и выберите Консоль, чтобы просмотреть журналы. Возможно, потребуется обновить страницу, чтобы просмотреть журналы SignalR, которые согласовывают метод транспорта для нового подключения.
Если вы используете Internet Обозреватель 10 на Windows 8 (IIS 8), методом транспорта будет WebSockets.
Если вы используете Интернет Обозреватель 10 в Windows 7 (IIS 7.5), методом транспорта является iframe.
Если вы используете Firefox 19 на Windows 8 (IIS 8), методом транспорта будет WebSocket.
Совет
В Firefox установите надстройку Firebug, чтобы получить окно консоли.
Если вы используете Firefox 19 в Windows 7 (IIS 7.5), методом транспорта являются события , отправляемые сервером .
Установка примера StockTicker
Microsoft.AspNet.SignalR.Sample устанавливает приложение StockTicker. Пакет NuGet содержит больше возможностей, чем упрощенная версия, созданная с нуля. В этом разделе учебника вы установите пакет NuGet и изучите новые функции и код, который их реализует.
Важно!
Если вы устанавливаете пакет без выполнения предыдущих шагов, описанных в этом руководстве, необходимо добавить класс запуска OWIN в проект. Этот шаг описан в файле readme.txt для пакета NuGet.
Установка пакета NuGet SignalR.Sample
В обозревателе решений щелкните проект правой кнопкой мыши и выберите Управление пакетами NuGet.
В диспетчере пакетов NuGet: SignalR.StockTicker нажмите кнопку Обзор.
В поле Источник пакета выберите nuget.org.
Введите SignalR.Sample в поле поиска и выберите Microsoft.AspNet.SignalR.Sample>Install.
В Обозреватель решений разверните папку SignalR.Sample.
При установке пакета SignalR.Sample была создана папка и ее содержимое.
В папке SignalR.Sample щелкните правой кнопкой мыши StockTicker.html, а затем выберите пункт Задать как начальную страницу.
Примечание
Установка пакета NuGet SignalR.Sample может изменить версию jQuery в папке Scripts . Новый файлStockTicker.html , который пакет устанавливает в папке SignalR.Sample , будет синхронизирован с версией jQuery, устанавливаемой пакетом, но если вы хотите снова запустить исходный StockTicker.html файл, может потребоваться сначала обновить ссылку jQuery в теге скрипта.
Выполнение приложения
Таблица, которую вы видели в первом приложении, имела полезные функции. В приложении полного тикера акций отображаются новые функции: горизонтальное прокручиваемое окно с данными о акциях и акциями, которые изменяют цвет по мере их роста и падения.
Нажмите клавишу F5 , чтобы запустить приложение.
При первом запуске приложения "рынок" закрывается, и вы увидите статическую таблицу и окно с тикером, которое не прокручивается.
Выберите Открыть рынок.
Поле Live Stock Ticker начинает прокручиваться по горизонтали, а сервер начинает периодически транслировать изменения цен на акции на случайной основе.
Каждый раз, когда цена акций меняется, приложение обновляет как таблицу live stock , так и live stock ticker.
Если цена на акции изменяется положительно, приложение отображает акции с зеленым фоном.
Если изменение отрицательное, приложение отображает акции на красном фоне.
Выберите Закрыть рынок.
Обновление таблицы останавливается.
Тикер останавливает прокрутку.
Выберите Сброс.
Все данные акций сбрасываются.
Приложение восстанавливает исходное состояние до начала изменения цен.
Скопируйте URL-адрес из браузера, откройте два других браузера и вставьте URL-адреса в адресные строки.
Одни и те же данные динамически обновляются одновременно в каждом браузере.
При выборе любого из элементов управления все браузеры отвечают одинаково.
Дисплей Live Stock Ticker
Отображение Live Stock Ticker — это неупорядоченный список в элементе <div>
, отформатированный в одну строку стилей CSS. Приложение инициализирует и обновляет тикер так же, как и таблица: заменяя заполнители в строке <li>
шаблона и динамически добавляя <li>
элементы в <ul>
элемент . Приложение включает прокрутку с помощью функции jQuery animate
для изменения левого края неупорядоченного списка в <div>
.
StockTicker.html SignalR.Sample
HTML-код stock ticker:
<h2>Live Stock Ticker</h2>
<div id="stockTicker">
<div class="inner">
<ul>
<li class="loading">loading...</li>
</ul>
</div>
</div>
SignalR.Sample StockTicker.css
Код CSS для тикера акций:
#stockTicker {
overflow: hidden;
width: 450px;
height: 24px;
border: 1px solid #999;
}
#stockTicker .inner {
width: 9999px;
}
#stockTicker ul {
display: inline-block;
list-style-type: none;
margin: 0;
padding: 0;
}
#stockTicker li {
display: inline-block;
margin-right: 8px;
}
/*<li data-symbol="{Symbol}"><span class="symbol">{Symbol}</span><span class="price">{Price}</span><span class="change">{PercentChange}</span></li>*/
#stockTicker .symbol {
font-weight: bold;
}
#stockTicker .change {
font-style: italic;
}
SignalR.StockTicker.js SignalR.Sample
Код jQuery, который выполняет прокрутку:
function scrollTicker() {
var w = $stockTickerUl.width();
$stockTickerUl.css({ marginLeft: w });
$stockTickerUl.animate({ marginLeft: -w }, 15000, 'linear', scrollTicker);
}
Дополнительные методы на сервере, которые клиент может вызывать
Для повышения гибкости приложения существуют дополнительные методы, которые приложение может вызывать.
SignalR.Sample StockTickerHub.cs
Класс StockTickerHub
определяет четыре дополнительных метода, которые клиент может вызывать:
public string GetMarketState()
{
return _stockTicker.MarketState.ToString();
}
public void OpenMarket()
{
_stockTicker.OpenMarket();
}
public void CloseMarket()
{
_stockTicker.CloseMarket();
}
public void Reset()
{
_stockTicker.Reset();
}
Приложение вызывает OpenMarket
, CloseMarket
и Reset
в ответ на кнопки в верхней части страницы. Они демонстрируют шаблон одного клиента, инициирующего изменение состояния, немедленно распространяемого на всех клиентов. Каждый из этих методов вызывает метод в StockTicker
классе , который вызывает изменение состояния рынка, а затем транслирует новое состояние.
SignalR.Sample StockTicker.cs
StockTicker
В классе приложение поддерживает состояние рынка с MarketState
помощью свойства, возвращающего значение перечисленияMarketState
:
public MarketState MarketState
{
get { return _marketState; }
private set { _marketState = value; }
}
public enum MarketState
{
Closed,
Open
}
Каждый из методов, изменяющих состояние рынка, делает это внутри блока блокировки, так как StockTicker
класс должен быть потокобезопасный:
public void OpenMarket()
{
lock (_marketStateLock)
{
if (MarketState != MarketState.Open)
{
_timer = new Timer(UpdateStockPrices, null, _updateInterval, _updateInterval);
MarketState = MarketState.Open;
BroadcastMarketStateChange(MarketState.Open);
}
}
}
public void CloseMarket()
{
lock (_marketStateLock)
{
if (MarketState == MarketState.Open)
{
if (_timer != null)
{
_timer.Dispose();
}
MarketState = MarketState.Closed;
BroadcastMarketStateChange(MarketState.Closed);
}
}
}
public void Reset()
{
lock (_marketStateLock)
{
if (MarketState != MarketState.Closed)
{
throw new InvalidOperationException("Market must be closed before it can be reset.");
}
LoadDefaultStocks();
BroadcastMarketReset();
}
}
Чтобы убедиться, что этот код является потокобезопасным, поле, _marketState
которое поддерживает MarketState
свойство, назначенное volatile
:
private volatile MarketState _marketState;
Методы BroadcastMarketStateChange
и BroadcastMarketReset
похожи на метод BroadcastStockPrice, который вы уже видели, за исключением того, что они вызывают различные методы, определенные в клиенте:
private void BroadcastMarketStateChange(MarketState marketState)
{
switch (marketState)
{
case MarketState.Open:
Clients.All.marketOpened();
break;
case MarketState.Closed:
Clients.All.marketClosed();
break;
default:
break;
}
}
private void BroadcastMarketReset()
{
Clients.All.marketReset();
}
Дополнительные функции на клиенте, которые сервер может вызывать
Теперь updateStockPrice
функция обрабатывает как таблицу, так и отображение тикера и использует jQuery.Color
для вспышки красные и зеленые цвета.
Новые функции в SignalR.StockTicker.js включать и отключать кнопки в зависимости от состояния рынка. Они также останавливают или запускают горизонтальную прокрутку Live Stock Ticker . Так как многие функции добавляются в ticker.client
, приложение использует функцию расширения jQuery для их добавления.
$.extend(ticker.client, {
updateStockPrice: function (stock) {
var displayStock = formatStock(stock),
$row = $(rowTemplate.supplant(displayStock)),
$li = $(liTemplate.supplant(displayStock)),
bg = stock.LastChange === 0
? '255,216,0' // yellow
: stock.LastChange > 0
? '154,240,117' // green
: '255,148,148'; // red
$stockTableBody.find('tr[data-symbol=' + stock.Symbol + ']')
.replaceWith($row);
$stockTickerUl.find('li[data-symbol=' + stock.Symbol + ']')
.replaceWith($li);
$row.flash(bg, 1000);
$li.flash(bg, 1000);
},
marketOpened: function () {
$("#open").prop("disabled", true);
$("#close").prop("disabled", false);
$("#reset").prop("disabled", true);
scrollTicker();
},
marketClosed: function () {
$("#open").prop("disabled", false);
$("#close").prop("disabled", true);
$("#reset").prop("disabled", false);
stopTicker();
},
marketReset: function () {
return init();
}
});
Дополнительная настройка клиента после установки подключения
После того как клиент установит подключение, ему нужно выполнить некоторые дополнительные действия:
Узнайте, открыт ли рынок или закрыт для вызова соответствующей
marketOpened
функции илиmarketClosed
.Подключите вызовы метода сервера к кнопкам.
$.connection.hub.start()
.pipe(init)
.pipe(function () {
return ticker.server.getMarketState();
})
.done(function (state) {
if (state === 'Open') {
ticker.client.marketOpened();
} else {
ticker.client.marketClosed();
}
// Wire up the buttons
$("#open").click(function () {
ticker.server.openMarket();
});
$("#close").click(function () {
ticker.server.closeMarket();
});
$("#reset").click(function () {
ticker.server.reset();
});
});
Серверные методы не подключены к кнопкам до тех пор, пока приложение не установит подключение. Это значит, что код не может вызывать методы сервера до их появления.
Дополнительные ресурсы
В этом руководстве вы узнали, как запрограммировать приложение SignalR, которое передает сообщения с сервера всем подключенным клиентам. Теперь вы можете периодически транслировать сообщения в ответ на уведомления от любого клиента. Вы можете использовать концепцию однопоточного одноэлементного экземпляра для поддержания состояния сервера в сценариях многопользовательских онлайн-игр. Пример см. в разделе Игра ShootR на основе SignalR.
Руководства по сценариям однорангового взаимодействия см. в разделах начало работы с SignalR и Обновление в режиме реального времени с помощью SignalR.
Дополнительные сведения о SignalR см. в следующих ресурсах:
Дальнейшие действия
Изучив это руководство, вы:
- Создание проекта
- Настройка кода сервера
- Просмотр кода сервера
- Настройка клиентского кода
- Просмотр кода клиента
- тестирование приложения.
- Включено ведение журнала
Перейдите к следующей статье, чтобы узнать, как создать веб-приложение в режиме реального времени, использующее ASP.NET SignalR 2.