Высокочастотный режим реального времени с SignalR 1.x

Патрик Флетчер

Предупреждение

Эта документация не подходит для последней версии SignalR. Взгляните на ASP.NET Core SignalR.

В этом руководстве показано, как создать веб-приложение, использующее ASP.NET SignalR для обеспечения функций обмена сообщениями с высокой частотой. Высокочастотное обмен сообщениями в этом случае означает, что обновления отправляются с фиксированной скоростью; в случае этого приложения до 10 сообщений в секунду.

Приложение, которое вы создадите в этом руководстве, отображает фигуру, которую пользователи могут перетаскивать. Затем позиция фигуры во всех других подключенных браузерах будет обновлена, чтобы она соответствовала положению перетаскиваемой фигуры с помощью периодических обновлений.

Основные понятия, представленные в этом руководстве, содержат приложения в играх в режиме реального времени и других приложений моделирования.

Комментарии к руководству приветствуются. Если у вас есть вопросы, которые не связаны напрямую с руководством, их можно опубликовать на форуме ASP.NET SignalR или StackOverflow.com.

Обзор

В этом руководстве показано, как создать приложение, которое предоставляет общий доступ к состоянию объекта с другими браузерами в режиме реального времени. Приложение, которое мы создадим, называется MoveShape. Страница MoveShape отобразит элемент HTML Div, который пользователь может перетаскивать; Когда пользователь перетаскивает Div, его новое положение будет отправлено на сервер, который затем сообщит всем другим подключенным клиентам, чтобы обновить положение фигуры для сопоставления.

Снимок экрана: страница приложения MoveShape.

Приложение, созданное в этом руководстве, основано на демонстрации Дэмиана Эдвардса. Видео, содержащее эту демонстрацию, можно увидеть здесь.

В учебном пособии сначала будет показано, как отправлять сообщения SignalR из каждого события, которое срабатывает при перетаскивании фигуры. Затем каждый подключенный клиент обновляет положение локальной версии фигуры при каждом получении сообщения.

Хотя приложение будет работать с помощью этого метода, это не рекомендуемая программная модель, так как не было бы верхнего предела количества отправляемых сообщений, что может привести к перегрузке клиентов и сервера сообщениями и снижению производительности. Отображаемая анимация на клиенте также будет прерывистой, так как фигура будет мгновенно перемещаться каждым из методов, а не плавно перемещаться в каждое новое местоположение. В последующих разделах руководства показано, как создать функцию таймера, которая ограничивает максимальную скорость отправки сообщений клиентом или сервером и как плавно перемещать фигуру между расположениями. Последнюю версию приложения, созданного в этом руководстве, можно скачать из коллекции кода.

В этом руководстве содержатся следующие разделы:

Необходимые условия

Для работы с этим руководством требуется Visual Studio 2012 или Visual Studio 2010. Если используется Visual Studio 2010, проект будет использовать .NET Framework 4, а не .NET Framework 4.5.

Если вы используете Visual Studio 2012, рекомендуется установить обновление ASP.NET и web Tools 2012.2. Это обновление содержит новые функции, такие как улучшения публикации, новые функциональные возможности и новые шаблоны.

Если у вас есть Visual Studio 2010, убедитесь, что NuGet установлен.

Создание проекта

В этом разделе мы создадим проект в Visual Studio.

  1. В меню "Файл" выберите пункт "Создать проект".

  2. В диалоговом окне "Новый проект" разверните C# в разделе "Шаблоны " и выберите веб-сайт.

  3. Выберите шаблон пустого веб-приложения ASP.NET , назовите проект MoveShapeDemo и нажмите кнопку "ОК".

    Создание проекта

Добавление пакетов NuGet SignalR и JQuery.UI

Вы можете добавить функции SignalR в проект, установив пакет NuGet. В этом руководстве также будет использоваться пакет JQuery.UI для перетаскивания фигуры и анимации.

  1. Щелкните "Сервис" | Диспетчер пакетов NuGet | Консоль диспетчера пакетов.

  2. Введите следующую команду в диспетчере пакетов.

    Install-Package Microsoft.AspNet.SignalR -Version 1.1.3
    

    Пакет SignalR устанавливает ряд других пакетов NuGet в качестве зависимостей. После завершения установки у вас будут все компоненты сервера и клиента, необходимые для использования SignalR в приложении ASP.NET.

  3. Введите следующую команду в консоль диспетчера пакетов, чтобы установить пакеты JQuery и JQuery.UI.

    Install-Package jQuery.ui.combined
    

Создание базового приложения

В этом разделе мы создадим браузерное приложение, которое отправляет расположение фигуры на сервер во время каждого события перемещения мыши. Затем сервер передает эти сведения всем другим подключенным клиентам по мере получения. Мы развернем это приложение в последующих разделах.

  1. В обозревателе решений щелкните проект правой кнопкой мыши и выберите "Добавить", "Класс...". Назовите класс MoveShapeHub и нажмите кнопку "Добавить".

  2. Замените код в новом классе MoveShapeHub следующим кодом.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.AspNet.SignalR;
    using Microsoft.AspNet.SignalR.Hubs;
    using Newtonsoft.Json;
    
    namespace MoveShapeDemo
    {
        public class MoveShapeHub : Hub
        {
            public void UpdateModel(ShapeModel clientModel)
            {
                clientModel.LastUpdatedBy = Context.ConnectionId;
                // Update the shape model within our broadcaster
                Clients.AllExcept(clientModel.LastUpdatedBy).updateShape(clientModel);
            }
        }
        public class ShapeModel
        {
            // We declare Left and Top as lowercase with 
            // JsonProperty to sync the client and server models
            [JsonProperty("left")]
            public double Left { get; set; }
    
            [JsonProperty("top")]
            public double Top { get; set; }
    
            // We don't want the client to get the "LastUpdatedBy" property
            [JsonIgnore]
            public string LastUpdatedBy { get; set; }
        }
    }
    

    Приведенный MoveShapeHub выше класс представляет собой реализацию концентратора SignalR. Как и в руководстве 'Начало работы с SignalR', хаб имеет метод, который клиенты будут вызывать напрямую. В этом случае клиент отправит объект, содержащий новые координаты X и Y фигуры на сервер, который затем передается всем другим подключенным клиентам. SignalR автоматически сериализует этот объект с помощью JSON.

    Объект, который будет отправлен клиенту (ShapeModel) содержит элементы для хранения позиции фигуры. Версия объекта на сервере также содержит элемент для отслеживания, чьи данные клиента хранятся, чтобы данные не отправлялись обратно тому же клиенту. Этот член использует атрибут JsonIgnore, чтобы предотвратить его сериализацию и отправку клиенту.

  3. Затем мы настроим концентратор при запуске приложения. В обозревателе решений щелкните проект правой кнопкой мыши и нажмите кнопку "Добавить |" Глобальный класс приложений. Примите имя Global по умолчанию и нажмите ОК.

    Добавление глобального класса приложений

  4. Добавьте следующую using инструкцию после предоставленных инструкций using в классе Global.asax.cs.

    using System.Web.Routing;
    
  5. Добавьте следующую строку кода в Application_Start метод Глобального класса, чтобы зарегистрировать маршрут по умолчанию для SignalR.

    RouteTable.Routes.MapHubs();
    

    Файл global.asax должен выглядеть следующим образом:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Security;
    using System.Web.SessionState;
    
    using System.Web.Routing;
    
    namespace MoveShapeDemo
    {
        public class Global : System.Web.HttpApplication
        {
            protected void Application_Start(object sender, EventArgs e)
            {
                RouteTable.Routes.MapHubs();
            }
        }
    }
    
  6. Затем мы добавим клиента. В обозревателе решений щелкните проект правой кнопкой мыши и нажмите кнопку "Добавить |" Новый элемент. В диалоговом окне "Добавление нового элемента" выберите html-страницу. Присвойте странице соответствующее имя (например ,Default.html) и нажмите кнопку "Добавить".

  7. В обозревателе решений щелкните правой кнопкой мыши только что созданную страницу и нажмите кнопку "Задать как начальную страницу".

  8. Замените код по умолчанию на HTML-странице следующим фрагментом кода.

    Замечание

    Убедитесь, что приведенные ниже ссылки на скрипт соответствуют пакетам, добавленным в проект в папке "Скрипты". В Visual Studio 2010 версия JQuery и SignalR, добавленная в проект, может не совпадать с номерами версий ниже.

    <!DOCTYPE html>
    <html>
    <head>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
            }
        </style>
    </head>
    <body>
    <script src="Scripts/jquery-1.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
     $(function () {
                var moveShapeHub = $.connection.moveShapeHub,
                $shape = $("#shape"),
    
                shapeModel = {
                    left: 0,
                    top: 0
                };
    
                moveShapeHub.client.updateShape = function (model) {
                    shapeModel = model;
                    $shape.css({ left: model.left, top: model.top });
                };
    
                $.connection.hub.start().done(function () {
                    $shape.draggable({
                        drag: function () {
                            shapeModel = $shape.offset();
                            moveShapeHub.server.updateModel(shapeModel);
                        }
                    });
                });
            });
    </script>
    
        <div id="shape" />
    </body>
    </html>
    

    Приведенный выше код HTML и JavaScript создает красную фигуру с именем Shape, позволяет выполнять перетаскивание фигуры с помощью библиотеки jQuery и использует событие фигуры drag для отправки позиции фигуры на сервер.

  9. Запустите приложение, нажав клавишу F5. Скопируйте URL-адрес страницы и вставьте его во второе окно браузера. Перетащите фигуру в одном из окон браузера; Фигура в другом окне браузера должна перемещаться.

    Скриншот, показывающий, как при перетаскивании фигуры в одном окне браузера, она перемещается в другом окне.

Добавьте цикл клиента

Так как отправка расположения фигуры на каждом событии перемещения мыши создаст ненужный объем сетевого трафика, сообщения от клиента необходимо ограничивать. Мы будем использовать функцию Javascript setInterval для настройки цикла, который отправляет новые сведения о положении на сервер с фиксированной скоростью. Этот цикл является очень базовым представлением "игрового цикла", многократно называемой функцией, которая управляет всеми функциями игры или другого моделирования.

  1. Обновите клиентский код на HTML-странице, чтобы он соответствовал следующему фрагменту кода.

    <!DOCTYPE html>
    <html>
    <head>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
            }
        </style>
    
    </head>
    <body>
    <script src="Scripts/jquery-1.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
            $(function () {
                var moveShapeHub = $.connection.moveShapeHub,
                    $shape = $("#shape"),
                    // Send a maximum of 10 messages per second 
                    // (mouse movements trigger a lot of messages)
                    messageFrequency = 10, 
                    // Determine how often to send messages in
                    // time to abide by the messageFrequency
                    updateRate = 1000 / messageFrequency, 
                    shapeModel = {
                        left: 0,
                        top: 0
                    },
                    moved = false;
    
                moveShapeHub.client.updateShape = function (model) {
                    shapeModel = model;
                    $shape.css({ left: model.left, top: model.top });
                };
    
                $.connection.hub.start().done(function () {
                    $shape.draggable({
                        drag: function () {
                            shapeModel = $shape.offset();
                            moved = true;
                        }
                    });
    
                    // Start the client side server update interval
                    setInterval(updateServerModel, updateRate);
                });
    
                function updateServerModel() {
                    // Only update server if we have a new movement
                    if (moved) {
                        moveShapeHub.server.updateModel(shapeModel);
                        moved = false;
                    }
                }
            });
    </script>
    
        <div id="shape" />
    </body>
    </html>
    

    Приведенное выше обновление добавляет updateServerModel функцию, которая вызывается на фиксированной частоте. Эта функция отправляет данные позиции на сервер всякий раз, когда moved флаг указывает на наличие новых данных позиции для отправки.

  2. Запустите приложение, нажав клавишу F5. Скопируйте URL-адрес страницы и вставьте его во второе окно браузера. Перетащите фигуру в одном из окон браузера; Фигура в другом окне браузера должна перемещаться. Так как количество сообщений, отправляемых на сервер, будет регулироваться, анимация будет отображаться не так гладко, как в предыдущем разделе.

    Снимок экрана, показывающий, как фигура, перетаскиваемая в одном окне браузера, перемещается в другом окне при добавлении цикла клиента.

Добавьте цикл сервера

В текущем приложении сообщения, отправляемые с сервера клиенту, выходят так часто, как они получены. Это представляет аналогичную проблему, как это наблюдалось на клиенте; сообщения могут отправляться чаще, чем они необходимы, и подключение может стать перегруженным в результате. В этом разделе описывается, как обновить сервер для реализации таймера, который регулирует частоту исходящих сообщений.

  1. Замените содержимое MoveShapeHub.cs следующим фрагментом кода.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    using System.Threading;
    using Microsoft.AspNet.SignalR;
    
    using Newtonsoft.Json;
    
    namespace MoveShapeDemo
    {
        public class Broadcaster
        {
            private readonly static Lazy<Broadcaster> _instance = 
                new Lazy<Broadcaster>(() => new Broadcaster());
            // We're going to broadcast to all clients a maximum of 25 times per second
            private readonly TimeSpan BroadcastInterval = 
                TimeSpan.FromMilliseconds(40); 
            private readonly IHubContext _hubContext;
            private Timer _broadcastLoop;
            private ShapeModel _model;
            private bool _modelUpdated;
    
            public Broadcaster()
            {
                // Save our hub context so we can easily use it 
                // to send to its connected clients
                _hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();
    
                _model = new ShapeModel();
                _modelUpdated = false;
    
                // Start the broadcast loop
                _broadcastLoop = new Timer(
                    BroadcastShape, 
                    null, 
                    BroadcastInterval, 
                    BroadcastInterval);
            }
    
            public void BroadcastShape(object state)
            {
                // No need to send anything if our model hasn't changed
                if (_modelUpdated)
                {
                    // This is how we can access the Clients property 
                    // in a static hub method or outside of the hub entirely
                    _hubContext.Clients.AllExcept(_model.LastUpdatedBy).updateShape(_model);
                    _modelUpdated = false;
                }
            }
    
            public void UpdateShape(ShapeModel clientModel)
            {
                _model = clientModel;
                _modelUpdated = true;
            }
    
            public static Broadcaster Instance
            {
                get
                {
                    return _instance.Value;
                }
            }
        }
        
        public class MoveShapeHub : Hub
        {
            // Is set via the constructor on each creation
            private Broadcaster _broadcaster;
    
            public MoveShapeHub()
                : this(Broadcaster.Instance)
            {
            }
    
            public MoveShapeHub(Broadcaster broadcaster)
            {
                _broadcaster = broadcaster;
            }
    
            public void UpdateModel(ShapeModel clientModel)
            {
                clientModel.LastUpdatedBy = Context.ConnectionId;
                // Update the shape model within our broadcaster
                _broadcaster.UpdateShape(clientModel);
            }
        }
        public class ShapeModel
        {
            // We declare Left and Top as lowercase with 
            // JsonProperty to sync the client and server models
            [JsonProperty("left")]
            public double Left { get; set; }
    
            [JsonProperty("top")]
            public double Top { get; set; }
    
            // We don't want the client to get the "LastUpdatedBy" property
            [JsonIgnore]
            public string LastUpdatedBy { get; set; }
        }
        
    }
    

    Приведенный выше код расширяет клиент, чтобы добавить Broadcaster класс, который регулирует исходящие сообщения с помощью Timer класса из платформы .NET.

    Так как сам концентратор является транзиторным (он создается каждый раз, когда это необходимо), Broadcaster будет создан как одиночный экземпляр. Отложенная инициализация (представленная в .NET 4) используется для отсрочки его создания до тех пор, пока это не потребуется, обеспечивая полное создание первого экземпляра хаба до запуска таймера.

    Затем вызов функции клиента UpdateShape перемещается из метода концентратора UpdateModel, так что он больше не вызывается немедленно при получении входящих сообщений. Вместо этого сообщения клиентам будут отправляться с частотой 25 вызовов в секунду, управляемых _broadcastLoop таймером из Broadcaster класса.

    Наконец, вместо того чтобы вызывать метод клиента напрямую из концентратора, класс Broadcaster должен получить ссылку на текущий работающий концентратор (_hubContext) с помощью GlobalHost.

  2. Запустите приложение, нажав клавишу F5. Скопируйте URL-адрес страницы и вставьте его во второе окно браузера. Перетащите фигуру в одном из окон браузера; Фигура в другом окне браузера должна перемещаться. В браузере не будет видимой разницы из предыдущего раздела, но количество сообщений, отправляемых клиенту, будет регулироваться.

    Снимок экрана, демонстрирующий, как перемещение фигуры в одном окне браузера отображается в другом окне при добавлении серверного цикла.

Добавление плавной анимации на клиенте

Приложение почти полностью завершено, но мы могли бы сделать еще одно улучшение в движении фигуры на стороне клиента, когда она перемещается в ответ на сообщения сервера. Вместо того чтобы задать положение фигуры в новом расположении, заданном сервером, мы будем использовать функцию библиотеки animate пользовательского интерфейса JQuery для плавного перемещения фигуры между текущей и новой позицией.

  1. Обновите метод клиента updateShape, чтобы стать таким, как показан в выделенном коде ниже.

    <!DOCTYPE html>
    <html>
    <head>
        <title>SignalR MoveShape Demo</title>
        <style>
            #shape {
                width: 100px;
                height: 100px;
                background-color: #FF0000;
            }
        </style>
    
    </head>
    <body>
    <script src="Scripts/jquery-1.6.4.js"></script>
    <script src="Scripts/jquery-ui-1.10.2.js"></script>
    <script src="Scripts/jquery.signalR-1.0.1.js"></script>
    <script src="/signalr/hubs"></script>
    <script>
            $(function () {
                var moveShapeHub = $.connection.moveShapeHub,
                    $shape = $("#shape"),
                    // Send a maximum of 10 messages per second 
                    // (mouse movements trigger a lot of messages)
                    messageFrequency = 10, 
                    // Determine how often to send messages in
                    // time to abide by the messageFrequency
                    updateRate = 1000 / messageFrequency, 
                    shapeModel = {
                        left: 0,
                        top: 0
                    },
                    moved = false;
    
                 moveShapeHub.client.updateShape = function (model) {
                     shapeModel = model;
                     // Gradually move the shape towards the new location (interpolate)
                     // The updateRate is used as the duration because by the time 
                     // we get to the next location we want to be at the "last" location
                     // We also clear the animation queue so that we start a new 
                     // animation and don't lag behind.
                     $shape.animate(shapeModel, { duration: updateRate, queue: false });
                };
    
                $.connection.hub.start().done(function () {
                    $shape.draggable({
                        drag: function () {
                            shapeModel = $shape.offset();
                            moved = true;
                        }
                    });
    
                    // Start the client side server update interval
                    setInterval(updateServerModel, updateRate);
                });
    
                function updateServerModel() {
                    // Only update server if we have a new movement
                    if (moved) {
                        moveShapeHub.server.updateModel(shapeModel);
                        moved = false;
                    }
                }
            });
    </script>
    
        <div id="shape" />
    </body>
    </html>
    

    Приведенный выше код перемещает фигуру из старого расположения в новое, заданное сервером в течение интервала анимации (в данном случае 100 миллисекундах). Любая предыдущая анимация, выполняемая на фигуре, очищается до запуска новой анимации.

  2. Запустите приложение, нажав клавишу F5. Скопируйте URL-адрес страницы и вставьте его во второе окно браузера. Перетащите фигуру в одном из окон браузера; Фигура в другом окне браузера должна перемещаться. Перемещение фигуры в другом окне должно отображаться менее дерганым, так как его перемещение интерполируется с течением времени, а не устанавливается один раз для каждого входящего сообщения.

    Снимок экрана: перетаскивание фигуры в одном окне браузера в другом окне при добавлении плавной анимации на клиенте.

Дальнейшие шаги

В этом руководстве вы узнали, как программировать приложение SignalR, которое отправляет сообщения высокой частоты между клиентами и серверами. Эта парадигма коммуникации полезна для разработки онлайн-игр и других имитаций, таких как игра ShootR, созданная с помощью SignalR.

Полное приложение, созданное в этом руководстве, можно скачать из коллекции кода.

Дополнительные сведения о концепциях разработки SignalR см. на следующих сайтах для исходного кода и ресурсов SignalR: