Partager via


Créer une application de diffusion en continu de code en temps réel avec Socket.IO et l’héberger sur Azure

La création d’une expérience en temps réel comme la fonctionnalité de cocréation dans Microsoft Word peut être difficile.

Grâce à ses API faciles à utiliser, Socket.IO s’est avéré être une bibliothèque pour la communication en temps réel entre les clients et un serveur. Toutefois, Socket.IO utilisateurs signalent souvent des difficultés concernant la mise à l’échelle des connexions socket.IO. Avec Web PubSub pour Socket.IO, les développeurs n’ont plus besoin de s’inquiéter de la gestion des connexions persistantes.

Vue d’ensemble

Cet article explique comment créer une application qui permet à un coder de diffuser en continu des activités de codage vers un public. Vous générez cette application à l’aide de :

  • Éditeur Monaco, éditeur de code qui alimente Visual Studio Code.
  • Express, infrastructure web Node.js.
  • API que la bibliothèque Socket.IO fournit pour la communication en temps réel.
  • Héberger des connexions Socket.IO qui utilisent Web PubSub pour Socket.IO.

L’application terminée

L’application terminée permet à l’utilisateur d’un éditeur de code de partager un lien web via lequel les utilisateurs peuvent regarder la saisie.

Screenshot of the finished code-streaming app.

Pour garder les procédures prioritaires et digestibles en environ 15 minutes, cet article définit deux rôles d’utilisateur et ce qu’ils peuvent faire dans l’éditeur :

  • Un enregistreur, qui peut taper dans l’éditeur en ligne et le contenu est diffusé en continu
  • Visionneuses, qui reçoivent du contenu en temps réel tapé par l’enregistreur et ne peuvent pas modifier le contenu

Architecture

Article Objectif Avantages
bibliothèque Socket.IO Fournit un mécanisme d’échange de données bidirectionnel à faible latence entre l’application principale et les clients API faciles à utiliser qui couvrent la plupart des scénarios de communication en temps réel
Web PubSub pour Socket.IO Héberge WebSocket ou des connexions persistantes basées sur des sondages avec des clients Socket.IO Prise en charge de 100 000 connexions simultanées ; architecture d’application simplifiée

Diagram that shows how the Web PubSub for Socket.IO service connects clients with a server.

Prérequis

Pour suivre toutes les étapes décrites dans cet article, vous avez besoin des éléments suivants :

Créer une ressource Web PubSub pour Socket.IO

Utilisez Azure CLI pour créer la ressource :

az webpubsub create -n <resource-name> \
                    -l <resource-location> \
                    -g <resource-group> \
                    --kind SocketIO \
                    --sku Free_F1

Obtenir une chaîne de connexion

Un chaîne de connexion vous permet de vous connecter à Web PubSub pour Socket.IO.

Exécutez les commandes suivantes : Conservez le chaîne de connexion retourné quelque part, car vous en aurez besoin lorsque vous exécutez l’application plus loin dans cet article.

az webpubsub key show -n <resource-name> \ 
                      -g <resource-group> \ 
                      --query primaryKey \
                      -o tsv

Écrire le code côté serveur de l’application

Commencez à écrire le code de votre application en travaillant côté serveur.

Générer un serveur HTTP

  1. Créez un projet Node.js :

    mkdir codestream
    cd codestream
    npm init
    
  2. Installez le Kit de développement logiciel (SDK) serveur et Express :

    npm install @azure/web-pubsub-socket.io
    npm install express
    
  3. Importez les packages requis et créez un serveur HTTP pour traiter des fichiers statiques :

    /*server.js*/
    
    // Import required packages
    const express = require('express');
    const path = require('path');
    
    // Create an HTTP server based on Express
    const app = express();
    const server = require('http').createServer(app);
    
    app.use(express.static(path.join(__dirname, 'public')));
    
  4. Définissez un point de terminaison appelé /negotiate. Un client writer atteint d’abord ce point de terminaison. Ce point de terminaison retourne une réponse HTTP. La réponse contient un point de terminaison que le client doit utiliser pour établir une connexion persistante. Elle retourne également une room valeur à laquelle le client est affecté.

    /*server.js*/
    app.get('/negotiate', async (req, res) => {
        res.json({
            url: endpoint
            room_id: Math.random().toString(36).slice(2, 7),
        });
    });
    
    // Make the Socket.IO server listen on port 3000
    io.httpServer.listen(3000, () => {
        console.log('Visit http://localhost:%d', 3000);
    });
    

Créer le serveur Web PubSub pour Socket.IO

  1. Importez web PubSub pour Socket.IO SDK et définissez les options suivantes :

    /*server.js*/
    const { useAzureSocketIO } = require("@azure/web-pubsub-socket.io");
    
    const wpsOptions = {
        hub: "codestream",
        connectionString: process.argv[2]
    }
    
  2. Créez un serveur Web PubSub pour Socket.IO serveur :

    /*server.js*/
    
    const io = require("socket.io")();
    useAzureSocketIO(io, wpsOptions);
    

Les deux étapes sont légèrement différentes de la façon dont vous créez normalement un serveur Socket.IO, comme décrit dans cette documentation Socket.IO. Avec ces deux étapes, votre code côté serveur peut décharger la gestion des connexions persistantes vers un service Azure. Avec l’aide d’un service Azure, votre serveur d’applications agit uniquement comme un serveur HTTP léger.

Implémenter une logique métier

Maintenant que vous avez créé un serveur Socket.IO hébergé par Web PubSub, vous pouvez définir la façon dont les clients et le serveur communiquent à l’aide des API de Socket.IO. Ce processus est appelé implémentation de la logique métier.

  1. Une fois qu’un client est connecté, le serveur d’applications indique au client qu’il est connecté en envoyant un événement personnalisé nommé login.

    /*server.js*/
    io.on('connection', socket => {
        socket.emit("login");
    });
    
  2. Chaque client émet deux événements auxquels le serveur peut répondre : joinRoom et sendToRoom. Une fois que le serveur obtient la room_id valeur qu’un client souhaite joindre, vous utilisez socket.join l’API socket.IO pour joindre le client cible à la salle spécifiée.

    /*server.js*/
    socket.on('joinRoom', async (message) => {
        const room_id = message["room_id"];
        await socket.join(room_id);
    });
    
  3. Une fois qu’un client est joint, le serveur informe le client du résultat réussi en envoyant un message événement. Lorsque le client reçoit un message événement avec un type de ackJoinRoom, le client peut demander au serveur d’envoyer l’état de l’éditeur le plus récent.

    /*server.js*/
    socket.on('joinRoom', async (message) => {
        // ...
        socket.emit("message", {
            type: "ackJoinRoom", 
            success: true 
        })
    });
    
    /*client.js*/
    socket.on("message", (message) => {
        let data = message;
        if (data.type === 'ackJoinRoom' && data.success) {
            sendToRoom(socket, `${room_id}-control`, { data: 'sync'});
        }
        // ... 
    });
    
  4. Lorsqu’un client envoie un sendToRoom événement au serveur, le serveur diffuse les modifications apportées à l’état de l’éditeur de code dans la salle spécifiée. Tous les clients de la salle peuvent désormais recevoir la dernière mise à jour.

    socket.on('sendToRoom', (message) => {
        const room_id = message["room_id"]
        const data = message["data"]
    
        socket.broadcast.to(room_id).emit("message", {
            type: "editorMessage",
            data: data
        });
    });
    

Écrire le code côté client de l’application

Maintenant que les procédures côté serveur sont terminées, vous pouvez travailler côté client.

Configuration initiale

Vous devez créer un client Socket.IO pour communiquer avec le serveur. La question est de savoir avec quel serveur le client doit établir une connexion persistante. Étant donné que vous utilisez Web PubSub pour Socket.IO, le serveur est un service Azure. Rappelez-vous que vous avez défini un itinéraire /negotiate pour servir les clients à Un point de terminaison web PubSub pour Socket.IO.

/*client.js*/

async function initialize(url) {
    let data = await fetch(url).json()

    updateStreamId(data.room_id);

    let editor = createEditor(...); // Create an editor component

    var socket = io(data.url, {
        path: "/clients/socketio/hubs/codestream",
    });

    return [socket, editor, data.room_id];
}

La initialize(url) fonction organise quelques opérations d’installation ensemble :

  • Récupère le point de terminaison vers un service Azure à partir de votre serveur HTTP
  • Crée une instance de l’éditeur Monaco
  • Établit une connexion persistante avec Web PubSub pour Socket.IO

Client Writer

Comme mentionné précédemment, vous avez deux rôles d’utilisateur côté client : writer et viewer. Tout ce que les types d’enregistreurs sont diffusés à l’écran de la visionneuse.

  1. Obtenez le point de terminaison sur Web PubSub pour Socket.IO et la room_id valeur :

    /*client.js*/
    
    let [socket, editor, room_id] = await initialize('/negotiate');
    
  2. Lorsque le client writer est connecté au serveur, le serveur envoie un login événement à l’enregistreur. L’enregistreur peut répondre en demandant au serveur de se joindre à une salle spécifiée. Toutes les 200 millisecondes, le client writer envoie l’état de l’éditeur le plus récent à la salle. Une fonction nommée flush organise la logique d’envoi.

    /*client.js*/
    
    socket.on("login", () => {
        updateStatus('Connected');
        joinRoom(socket, `${room_id}`);
        setInterval(() => flush(), 200);
        // Update editor content
        // ...
    });
    
  3. Si un enregistreur n’apporte aucune modification, flush() ne fait rien et retourne simplement. Sinon, les modifications apportées à l’état de l’éditeur sont envoyées à la salle.

    /*client.js*/
    
    function flush() {
        // No changes from editor need to be flushed
        if (changes.length === 0) return;
    
        // Broadcast the changes made to editor content
        sendToRoom(socket, room_id, {
            type: 'delta',
            changes: changes
            version: version++,
        });
    
        changes = [];
        content = editor.getValue();
    }
    
  4. Lorsqu’un nouveau client de visionneuse est connecté, la visionneuse doit obtenir l’état complet le plus récent de l’éditeur. Pour ce faire, un message qui contient des sync données est envoyé au client writer. Le message demande au client writer d’envoyer l’état complet de l’éditeur.

    /*client.js*/
    
    socket.on("message", (message) => {
        let data = message.data;
        if (data.data === 'sync') {
            // Broadcast the full content of the editor to the room
            sendToRoom(socket, room_id, {
                type: 'full',
                content: content
                version: version,
            });
        }
    });
    

Client visionneuse

  1. Comme le client writer, le client de visionneuse crée son client Socket.IO via initialize(). Lorsque le client de la visionneuse est connecté et reçoit un login événement du serveur, il demande au serveur de se joindre à la salle spécifiée. La requête room_id spécifie la salle.

    /*client.js*/
    
    let [socket, editor] = await initialize(`/register?room_id=${room_id}`)
    socket.on("login", () => {
        updateStatus('Connected');
        joinRoom(socket, `${room_id}`);
    });
    
  2. Lorsqu’un client de visionneuse reçoit un message événement du serveur et que le type de données est ackJoinRoom, le client de la visionneuse demande au client writer dans la salle d’envoyer l’état complet de l’éditeur.

    /*client.js*/
    
    socket.on("message", (message) => {
        let data = message;
        // Ensures the viewer client is connected
        if (data.type === 'ackJoinRoom' && data.success) { 
            sendToRoom(socket, `${id}`, { data: 'sync'});
        } 
        else //...
    });
    
  3. Si le type de données est editorMessage, le client de visionneuse met à jour l’éditeur en fonction de son contenu réel.

    /*client.js*/
    
    socket.on("message", (message) => {
        ...
        else 
            if (data.type === 'editorMessage') {
            switch (data.data.type) {
                case 'delta':
                    // ... Let editor component update its status
                    break;
                case 'full':
                    // ... Let editor component update its status
                    break;
            }
        }
    });
    
  4. Implémentez et sendToRoom() utilisez joinRoom() les API de Socket.IO :

    /*client.js*/
    
    function joinRoom(socket, room_id) {
        socket.emit("joinRoom", {
            room_id: room_id,
        });
    }
    
    function sendToRoom(socket, room_id, data) {
        socket.emit("sendToRoom", {
            room_id: room_id,
            data: data
        });
    }
    

Exécution de l'application

Localiser le dépôt

Les sections précédentes ont abordé la logique principale liée à la synchronisation de l’état de l’éditeur entre les visionneuses et l’enregistreur. Vous trouverez le code complet dans le référentiel d’exemples.

Cloner le référentiel

Vous pouvez cloner le référentiel et l’exécuter npm install pour installer les dépendances du projet.

Démarrer le serveur

node server.js <web-pubsub-connection-string>

Il s’agit de la chaîne de connexion que vous avez reçue à une étape antérieure.

Lire avec l’éditeur de code en temps réel

Ouvrez un onglet de navigateur. Ouvrez http://localhost:3000 un autre onglet avec l’URL affichée sur la première page web.

Si vous écrivez du code sous le premier onglet, vous devez voir votre saisie reflétée en temps réel sur l’autre onglet. Web PubSub pour Socket.IO facilite la transmission de messages dans le cloud. Votre express serveur sert uniquement le fichier statique index.html et le /negotiate point de terminaison.