Temps réel haute fréquence avec SignalR 1.x

par Patrick Fletcher

Avertissement

Cette documentation ne concerne pas la dernière version de SignalR. Consultez ASP.NET Core SignalR.

Ce tutoriel montre comment créer une application web qui utilise ASP.NET SignalR pour fournir des fonctionnalités de messagerie à haute fréquence. Dans ce cas, la messagerie à haute fréquence signifie les mises à jour qui sont envoyées à un débit fixe ; dans le cas de cette application, jusqu’à 10 messages par seconde.

L’application que vous allez créer dans ce tutoriel affiche une forme que les utilisateurs peuvent faire glisser. La position de la forme dans tous les autres navigateurs connectés sera ensuite mise à jour pour correspondre à la position de la forme déplacée à l’aide de mises à jour chrono timed.

Les concepts présentés dans ce tutoriel ont des applications dans les jeux en temps réel et d’autres applications de simulation.

Les commentaires sur le tutoriel sont les bienvenus. Si vous avez des questions qui ne sont pas directement liées au tutoriel, vous pouvez les publier sur le forum ASP.NET SignalR ou StackOverflow.com.

Vue d’ensemble

Ce tutoriel montre comment créer une application qui partage l’état d’un objet avec d’autres navigateurs en temps réel. L’application que nous allons créer s’appelle MoveShape. La page MoveShape affiche un élément HTML Div que l’utilisateur peut faire glisser ; lorsque l’utilisateur fait glisser le Div, sa nouvelle position est envoyée au serveur, qui indique ensuite à tous les autres clients connectés de mettre à jour la position de la forme pour qu’elle corresponde.

Capture d’écran montrant la page de l’application MoveShape.

L’application créée dans ce tutoriel est basée sur une démonstration de Damian Edwards. Une vidéo contenant cette démonstration est visible ici.

Le tutoriel commence par montrer comment envoyer des messages SignalR à partir de chaque événement qui se déclenche lorsque la forme est déplacée. Chaque client connecté met ensuite à jour la position de la version locale de la forme chaque fois qu’un message est reçu.

Bien que l’application fonctionne à l’aide de cette méthode, il ne s’agit pas d’un modèle de programmation recommandé, car il n’y aurait aucune limite supérieure au nombre de messages envoyés, de sorte que les clients et le serveur pourraient être submergés par les messages et les performances se dégraderaient. L’animation affichée sur le client serait également décousue, car la forme serait déplacée instantanément par chaque méthode, plutôt que de se déplacer en douceur vers chaque nouvel emplacement. Les sections ultérieures du tutoriel montreront comment créer une fonction minuterie qui limite la vitesse maximale d’envoi des messages par le client ou le serveur, et comment déplacer la forme en douceur entre les emplacements. La version finale de l’application créée dans ce didacticiel peut être téléchargée à partir de La galerie de codes.

Ce didacticiel contient les sections suivantes :

Prérequis

Ce didacticiel nécessite Visual Studio 2012 ou Visual Studio 2010. Si Visual Studio 2010 est utilisé, le projet utilisera .NET Framework 4 plutôt que .NET Framework 4.5.

Si vous utilisez Visual Studio 2012, il est recommandé d’installer la mise à jour ASP.NET et Web Tools 2012.2. Cette mise à jour contient de nouvelles fonctionnalités telles que les améliorations apportées à la publication, les nouvelles fonctionnalités et les nouveaux modèles.

Si vous disposez de Visual Studio 2010, assurez-vous que NuGet est installé.

Créer le projet

Dans cette section, nous allons créer le projet dans Visual Studio.

  1. Dans le menu Fichier , cliquez sur Nouveau projet.

  2. Dans la boîte de dialogue Nouveau projet , développez C# sous Modèles , puis sélectionnez Web.

  3. Sélectionnez le modèle d’application web vide ASP.NET , nommez le projet MoveShapeDemo, puis cliquez sur OK.

    Création du projet

Ajouter les packages NuGet SignalR et JQuery.UI

Vous pouvez ajouter la fonctionnalité SignalR à un projet en installant un package NuGet. Ce tutoriel utilise également le package JQuery.UI pour permettre le déplacement et l’animation de la forme.

  1. Cliquez sur Outils | Gestionnaire de package NuGet | Console du Gestionnaire de package.

  2. Entrez la commande suivante dans le gestionnaire de package.

    Install-Package Microsoft.AspNet.SignalR -Version 1.1.3
    

    Le package SignalR installe un certain nombre d’autres packages NuGet en tant que dépendances. Une fois l’installation terminée, vous disposez de tous les composants serveur et client nécessaires pour utiliser SignalR dans une application ASP.NET.

  3. Entrez la commande suivante dans la console du gestionnaire de package pour installer les packages JQuery et JQuery.UI.

    Install-Package jQuery.ui.combined
    

Créer l’application de base

Dans cette section, nous allons créer une application de navigateur qui envoie l’emplacement de la forme au serveur lors de chaque événement de déplacement de la souris. Le serveur diffuse ensuite ces informations à tous les autres clients connectés au fur et à mesure de leur réception. Nous développerons cette application dans les sections ultérieures.

  1. Dans Explorateur de solutions, cliquez avec le bouton droit sur le projet, puis sélectionnez Ajouter, Classe.... Nommez la classe MoveShapeHub, puis cliquez sur Ajouter.

  2. Remplacez le code de la nouvelle classe MoveShapeHub par le code suivant.

    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; }
        }
    }
    

    La MoveShapeHub classe ci-dessus est une implémentation d’un hub SignalR. Comme dans le tutoriel Prise en main avec SignalR, le hub a une méthode que les clients appellent directement. Dans ce cas, le client envoie un objet contenant les nouvelles coordonnées X et Y de la forme au serveur, qui est ensuite diffusé sur tous les autres clients connectés. SignalR sérialise automatiquement cet objet à l’aide de JSON.

    L’objet qui sera envoyé au client (ShapeModel) contient des membres pour stocker la position de la forme. La version de l’objet sur le serveur contient également un membre pour suivre les données du client qui sont stockées, afin qu’un client donné ne soit pas envoyé ses propres données. Ce membre utilise l’attribut JsonIgnore pour l’empêcher d’être sérialisé et envoyé au client.

  3. Ensuite, nous allons configurer le hub au démarrage de l’application. Dans Explorateur de solutions, cliquez avec le bouton droit sur le projet, puis cliquez sur Ajouter | Classe d’application globale. Acceptez le nom par défaut Global , puis cliquez sur OK.

    Ajouter une classe d’application globale

  4. Ajoutez l’instruction suivante using après les instructions using fournies dans la classe Global.asax.cs.

    using System.Web.Routing;
    
  5. Ajoutez la ligne de code suivante dans la Application_Start méthode de la classe Global pour inscrire l’itinéraire par défaut pour SignalR.

    RouteTable.Routes.MapHubs();
    

    Votre fichier global.asax doit ressembler à ce qui suit :

    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. Ensuite, nous allons ajouter le client. Dans Explorateur de solutions, cliquez avec le bouton droit sur le projet, puis cliquez sur Ajouter | Nouvel élément. Dans la boîte de dialogue Ajouter un nouvel élément , sélectionnez Page Html. Donnez à la page un nom approprié (par exemple ,Default.html) et cliquez sur Ajouter.

  7. Dans Explorateur de solutions, cliquez avec le bouton droit sur la page que vous venez de créer, puis cliquez sur Définir comme page de démarrage.

  8. Remplacez le code par défaut dans la page HTML par l’extrait de code suivant.

    Notes

    Vérifiez que les références de script ci-dessous correspondent aux packages ajoutés à votre projet dans le dossier Scripts. Dans Visual Studio 2010, la version de JQuery et SignalR ajoutée au projet peut ne pas correspondre aux numéros de version ci-dessous.

    <!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>
    

    Le code HTML et JavaScript ci-dessus crée une Div rouge appelée Shape, active le comportement de glissement de la forme à l’aide de la bibliothèque jQuery et utilise l’événement de drag la forme pour envoyer la position de la forme au serveur.

  9. Démarrez l’application en appuyant sur F5. Copiez l’URL de la page et collez-la dans une deuxième fenêtre de navigateur. Faites glisser la forme dans l’une des fenêtres du navigateur ; la forme de l’autre fenêtre de navigateur doit se déplacer.

    Capture d’écran montrant comment une forme que vous faites glisser dans une fenêtre de navigateur se déplace dans une autre fenêtre.

Ajouter la boucle cliente

Étant donné que l’envoi de l’emplacement de la forme sur chaque événement de déplacement de souris crée une quantité inutile de trafic réseau, les messages du client doivent être limités. Nous allons utiliser la fonction javascript setInterval pour configurer une boucle qui envoie de nouvelles informations de position au serveur à un débit fixe. Cette boucle est une représentation très basique d’une « boucle de jeu », une fonction appelée à plusieurs reprises qui pilote toutes les fonctionnalités d’un jeu ou d’une autre simulation.

  1. Mettez à jour le code client dans la page HTML pour qu’il corresponde à l’extrait de code suivant.

    <!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>
    

    La mise à jour ci-dessus ajoute la updateServerModel fonction, qui est appelée sur une fréquence fixe. Cette fonction envoie les données de position au serveur chaque fois que l’indicateur moved indique qu’il existe de nouvelles données de position à envoyer.

  2. Démarrez l’application en appuyant sur F5. Copiez l’URL de la page et collez-la dans une deuxième fenêtre de navigateur. Faites glisser la forme dans l’une des fenêtres du navigateur ; la forme de l’autre fenêtre de navigateur doit se déplacer. Étant donné que le nombre de messages envoyés au serveur est limité, l’animation n’apparaît pas aussi fluide que dans la section précédente.

    Capture d’écran montrant comment une forme que vous faites glisser dans une fenêtre de navigateur se déplace dans une autre fenêtre lorsque vous ajoutez une boucle cliente.

Ajouter la boucle du serveur

Dans l’application actuelle, les messages envoyés du serveur au client sortent aussi souvent qu’ils sont reçus. Cela présente un problème similaire à celui qui a été observé sur le client ; les messages peuvent être envoyés plus souvent qu’ils ne sont nécessaires, et la connexion peut être inondée en conséquence. Cette section explique comment mettre à jour le serveur pour implémenter un minuteur qui limite le débit des messages sortants.

  1. Remplacez le contenu de par l’extrait de MoveShapeHub.cs code suivant.

    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; }
        }
        
    }
    

    Le code ci-dessus développe le client pour ajouter la Broadcaster classe , qui limite les messages sortants à l’aide de la Timer classe du .NET Framework.

    Étant donné que le hub lui-même est transitoire (il est créé chaque fois qu’il est nécessaire), le Broadcaster est créé en tant que singleton. L’initialisation différée (introduite dans .NET 4) est utilisée pour différer sa création jusqu’à ce qu’elle soit nécessaire, garantissant ainsi que le premier instance hub est entièrement créé avant le démarrage du minuteur.

    L’appel à la fonction des UpdateShape clients est ensuite déplacé hors de la méthode du UpdateModel hub, de sorte qu’il n’est plus appelé immédiatement chaque fois que les messages entrants sont reçus. Au lieu de cela, les messages aux clients sont envoyés à un taux de 25 appels par seconde, gérés par le _broadcastLoop minuteur à partir de la Broadcaster classe .

    Enfin, au lieu d’appeler directement la méthode cliente à partir du hub, la Broadcaster classe doit obtenir une référence au hub en cours d’exploitation (_hubContext) à l’aide de GlobalHost.

  2. Démarrez l’application en appuyant sur F5. Copiez l’URL de la page et collez-la dans une deuxième fenêtre de navigateur. Faites glisser la forme dans l’une des fenêtres du navigateur ; la forme de l’autre fenêtre de navigateur doit se déplacer. Il n’y aura pas de différence visible dans le navigateur par rapport à la section précédente, mais le nombre de messages envoyés au client sera limité.

    Capture d’écran montrant comment une forme que vous faites glisser dans une fenêtre de navigateur se déplace dans une autre fenêtre lorsque vous ajoutez une boucle de serveur.

Ajouter une animation fluide sur le client

L’application est presque terminée, mais nous pourrions apporter une autre amélioration, dans le mouvement de la forme sur le client à mesure qu’elle est déplacée en réponse aux messages du serveur. Au lieu de définir la position de la forme sur le nouvel emplacement donné par le serveur, nous allons utiliser la fonction de animate la bibliothèque d’interface utilisateur JQuery pour déplacer la forme en douceur entre sa position actuelle et sa nouvelle position.

  1. Mettez à jour la méthode du client pour qu’elle updateShape ressemble au code en surbrillance ci-dessous :

    <!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>
    

    Le code ci-dessus déplace la forme de l’ancien emplacement vers le nouvel emplacement donné par le serveur au cours de l’intervalle d’animation (dans ce cas, 100 millisecondes). Toute animation précédente exécutée sur la forme est effacée avant le démarrage de la nouvelle animation.

  2. Démarrez l’application en appuyant sur F5. Copiez l’URL de la page et collez-la dans une deuxième fenêtre de navigateur. Faites glisser la forme dans l’une des fenêtres du navigateur ; la forme de l’autre fenêtre de navigateur doit se déplacer. Le mouvement de la forme dans l’autre fenêtre doit apparaître moins saccadé, car son mouvement est interpolé au fil du temps plutôt que d’être défini une seule fois par message entrant.

    Capture d’écran montrant comment une forme que vous faites glisser dans une fenêtre de navigateur se déplace dans une autre fenêtre lorsque vous ajoutez une animation fluide sur le client.

Étapes supplémentaires

Dans ce tutoriel, vous avez appris à programmer une application SignalR qui envoie des messages à haute fréquence entre les clients et les serveurs. Ce paradigme de communication est utile pour développer des jeux en ligne et d’autres simulations, comme le jeu ShootR créé avec SignalR.

L’application complète créée dans ce didacticiel peut être téléchargée à partir de la galerie de code.

Pour en savoir plus sur les concepts de développement SignalR, consultez les sites suivants pour le code source et les ressources SignalR :