Обмен сообщениями с высоким уровнем периодичности в режиме реального времени с помощью 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 and 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 метод класса Global, чтобы зарегистрировать маршрут по умолчанию для 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 создает красный элемент Div с именем 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 Framework.

    Так как сам концентратор является временным (он создается каждый раз, когда это необходимо), 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: