Freigeben über


Erstellen einer Codestreaming-App in Echtzeit mithilfe von Socket.IO und Hosten in Azure

Das Erstellen einer Echtzeiterfahrung wie das Feature für die gemeinsame Erstellung in Microsoft Word kann eine Herausforderung darstellen.

Durch seine benutzerfreundlichen APIs hat sich Socket.IO als Bibliothek für die Echtzeitkommunikation zwischen Clients und einem Server erwiesen. Socket.IO-Benutzer melden jedoch häufig Schwierigkeiten beim Skalieren der Verbindungen von Socket.IO. Mit Web PubSub für Socket.IO müssen Entwickler sich keine Gedanken mehr über das Verwalten dauerhafter Verbindungen machen.

Wichtig

Unformatierte Verbindungszeichenfolgen werden in diesem Artikel nur zu Demonstrationszwecken angezeigt.

Eine Verbindungszeichenfolge enthält die Autorisierungsinformationen, die Ihre Anwendung für den Zugriff auf den Azure Web PubSub-Dienst benötigt. Der Zugriffsschlüssel in der Verbindungszeichenfolge ähnelt einem Stammkennwort für Ihren Dienst. Schützen Sie Ihre Zugriffsschlüssel in Produktionsumgebungen immer sorgfältig. Verwenden Sie Azure Key Vault zum sicheren Verwalten und Rotieren Ihrer Schlüssel und zum Sichern Ihrer Verbindung mit WebPubSubServiceClient.

Geben Sie Zugriffsschlüssel nicht an andere Benutzer weiter, vermeiden Sie das Hartcodieren, und speichern Sie die Schlüssel nicht als Klartext, auf den andere Benutzer Zugriff haben. Rotieren Sie die Schlüssel, wenn Sie glauben, dass sie möglicherweise gefährdet sind.

Übersicht

In diesem Artikel erfahren Sie, wie Sie eine App erstellen, die es einem Programmierer ermöglicht, Codierungsaktivitäten an eine Zielgruppe zu streamen. Sie erstellen diese Anwendung mithilfe von:

  • Monaco-Editor, dem Code-Editor, der Visual Studio Code unterstützt.
  • Express, einem Node.js-Webframework.
  • APIs, die die Socket.IO-Bibliothek für die Echtzeitkommunikation bereitstellt.
  • Socket.IO-Hostverbindungen, die Web PubSub für Socket.IO verwenden.

Die fertige App

Die fertige App ermöglicht es dem Benutzer eines Code-Editors, einen Weblink freizugeben, über den Benutzer die Eingabe ansehen können.

Screenshot der fertigen Codestreaming-App.

Um die Prozeduren fokussiert und in etwa 15 Minuten verständlich zu halten, definiert dieser Artikel zwei Benutzerrollen und was sie im Editor tun können:

  • Einen Writer, der im Online-Editor eingeben kann und dessen Inhalt gestreamt wird
  • Viewer, die vom Writer eingegebenen Inhalt in Echtzeit erhalten und den Inhalt nicht bearbeiten können

Aufbau

Element Zweck Vorteile
Socket.IO-Bibliothek Bietet latenzarme, bidirektionale Datenaustauschmechanismen zwischen Back-End-Anwendung und Clients Benutzerfreundliche APIs, die die meisten Echtzeitkommunikationsszenarien abdecken
Web PubSub für Socket.IO Hosten von WebSocket- oder abfragebasierten dauerhaften Verbindungen mit Socket.IO-Clients Unterstützung für 100.000 gleichzeitige Verbindungen; vereinfachte Anwendungsarchitektur

Schematische Darstellung, die zeigt, wie der Web PubSub für Socket.IO-Dienst Clients mit einem Server verbindet.

Voraussetzungen

Damit Sie all den Schritten in diesem Artikel folgen können, benötigen Sie Folgendes:

Erstellen einer Web PubSub für Socket.IO-Ressource

Verwenden Sie die Azure CLI, um die Ressource zu erstellen:

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

Abrufen einer Verbindungszeichenfolge

Mit einer Verbindungszeichenfolge können Sie eine Verbindung mit Web PubSub für Socket.IO herstellen.

Führen Sie die folgenden Befehle aus. Bewahren Sie die zurückgegebene Verbindungszeichenfolge an einer beliebigen Stelle auf, da Sie sie benötigen, wenn Sie die Anwendung später in diesem Artikel ausführen.

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

Schreiben des serverseitigen Codes der Anwendung

Beginnen Sie mit dem Schreiben des Codes Ihrer Anwendung, indem Sie auf der Serverseite arbeiten.

Erstellen eines HTTP-Servers

  1. Erstellen Sie ein Node.js-Projekt:

    mkdir codestream
    cd codestream
    npm init
    
  2. Installieren Sie das Server-SDK und Express:

    npm install @azure/web-pubsub-socket.io
    npm install express
    
  3. Importieren Sie erforderliche Pakete, und erstellen Sie einen HTTP-Server zum Bereitstellen statischer Dateien:

    /*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. Definieren Sie einen Endpunkt mit dem Namen /negotiate. Ein Writer-Client erreicht diesen Endpunkt zuerst. Dieser Endpunkt gibt eine HTTP-Antwort zurück. Die Antwort enthält einen Endpunkt, mit dem der Client eine dauerhafte Verbindung herstellen soll. Außerdem wird ein room-Wert zurückgegeben, dem der Client zugewiesen ist.

    /*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);
    });
    

Erstellen des Web PubSub für Socket.IO-Servers

  1. Importieren Sie das Web PubSub für Socket.IO-SDK, und definieren Sie Optionen:

    Unformatierte Verbindungszeichenfolgen werden in diesem Artikel nur zu Demonstrationszwecken angezeigt. Schützen Sie Ihre Zugriffsschlüssel in Produktionsumgebungen immer sorgfältig. Verwenden Sie Azure Key Vault zum sicheren Verwalten und Rotieren Ihrer Schlüssel und zum Sichern Ihrer Verbindung mit WebPubSubServiceClient.

    /*server.js*/
    const { useAzureSocketIO } = require("@azure/web-pubsub-socket.io");
    
    const wpsOptions = {
        hub: "codestream",
        connectionString: process.argv[2]
    }
    
  2. Erstellen Sie einen Web PubSub für Socket.IO-Server:

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

Die beiden Schritte unterscheiden sich geringfügig davon, wie Sie normalerweise einen Socket.IO-Server erstellen würden, wie in dieser Socket.IO-Dokumentation beschrieben. Mit diesen beiden Schritten kann Ihr serverseitiger Code die Verwaltung dauerhafter Verbindungen an einen Azure-Dienst auslagern. Mithilfe eines Azure-Diensts fungiert Ihr Anwendungsserver nur als einfacher HTTP-Server.

Implementieren von Geschäftslogik

Nachdem Sie nun einen Socket.IO-Server erstellt haben, der von Web PubSub gehostet wird, können Sie definieren, wie die Clients und der Server mithilfe der APIs von Socket.IO kommunizieren. Dieser Prozess wird als Implementieren von Geschäftslogik bezeichnet.

  1. Nachdem ein Client verbunden wurde, teilt der Anwendungsserver dem Client mit, dass er angemeldet ist, indem ein benutzerdefiniertes Ereignis mit dem Namen login gesandt wird.

    /*server.js*/
    io.on('connection', socket => {
        socket.emit("login");
    });
    
  2. Jeder Client gibt zwei Ereignisse aus, auf die der Server reagieren kann: joinRoom und sendToRoom. Nachdem der Server den room_id-Wert abgerufen hat, mit dem ein Client verbunden werden möchte, verwenden Sie socket.join aus der API von Socket.IO, um den Zielclient mit dem angegebenen Raum zu verbinden.

    /*server.js*/
    socket.on('joinRoom', async (message) => {
        const room_id = message["room_id"];
        await socket.join(room_id);
    });
    
  3. Nachdem ein Client verbunden wurde, informiert der Server den Client über das erfolgreiche Ergebnis, indem ein message-Ereignis gesandt wird. Wenn der Client ein message-Ereignis mit einem Typ von ackJoinRoom empfängt, kann der Client den Server bitten, den neuesten Editorstatus zu senden.

    /*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. Wenn ein Client ein sendToRoom-Ereignis an den Server sendet, sendet der Server die Änderungen am Code-Editor-Status an den angegebenen Raum. Alle Clients im Raum können jetzt das neueste Update erhalten.

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

Schreiben des clientseitigen Codes der Anwendung

Nachdem die serverseitigen Prozeduren abgeschlossen sind, können Sie auf der Clientseite arbeiten.

Anfangssetup

Sie müssen einen Socket.IO-Client für die Kommunikation mit dem Server erstellen. Die Frage lautet, mit welchem Server der Client eine dauerhafte Verbindung herstellen soll. Da Sie Web PubSub für Socket.IO verwenden, ist der Server ein Azure-Dienst. Denken Sie daran, dass Sie eine /negotiate-Route definiert haben, um Clients einen Endpunkt für Web PubSub für Socket.IO bereitzustellen.

/*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];
}

Die initialize(url)-Funktion fasst einige Setupvorgänge zusammen:

  • Ruft den Endpunkt für einen Azure-Dienst von Ihrem HTTP-Server ab.
  • Erstellt eine Monaco-Editor-Instanz
  • Stellt eine dauerhafte Verbindung mit Web PubSub für Socket.IO her

Writer-Client

Wie zuvor erwähnt, verfügen Sie über zwei Benutzerrollen auf der Clientseite: Writer und Viewer. Alles, was der Writer eingibt, wird auf den Bildschirm des Viewers gestreamt.

  1. Rufen Sie den Endpunkt für Web PubSub für Socket.IO und den room_id-Wert ab:

    /*client.js*/
    
    let [socket, editor, room_id] = await initialize('/negotiate');
    
  2. Wenn der Writer-Client mit dem Server verbunden ist, sendet der Server ein login-Ereignis an den Writer. Der Writer kann antworten, indem er den Server auffordert, sich selbst mit einem bestimmten Raum zu verbinden. Alle 200 Millisekunden sendet der Writer-Client den neuesten Editorstatus an den Raum. Eine Funktion mit dem Namen flush organisiert die Sendelogik.

    /*client.js*/
    
    socket.on("login", () => {
        updateStatus('Connected');
        joinRoom(socket, `${room_id}`);
        setInterval(() => flush(), 200);
        // Update editor content
        // ...
    });
    
  3. Wenn ein Writer keine Bearbeitungen vornimmt, führt flush() nichts aus und wird einfach zurückgegeben. Andernfalls werden die Änderungen am Editorstatus an den Raum gesandt.

    /*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. Wenn ein neuer Viewer-Client verbunden ist, muss der Viewer den neuesten vollständigen Status des Editors abrufen. Um dies zu erreichen, wird eine Nachricht, die sync-Daten enthält, an den Writer-Client gesandt. Die Nachricht fordert den Writer-Client auf, den vollständigen Editorstatus zu senden.

    /*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,
            });
        }
    });
    

Viewer-Client

  1. Wie der Writer-Client erstellt der Viewer-Client seinen Socket.IO-Client über initialize(). Wenn der Viewer-Client verbunden ist und ein login-Ereignis vom Server empfängt, fordert er den Server auf, sich mit dem angegebenen Raum zu verbinden. Die Abfrage room_id gibt den Raum an.

    /*client.js*/
    
    let [socket, editor] = await initialize(`/register?room_id=${room_id}`)
    socket.on("login", () => {
        updateStatus('Connected');
        joinRoom(socket, `${room_id}`);
    });
    
  2. Wenn ein Viewer-Client ein message-Ereignis vom Server empfängt und der Datentyp ackJoinRoom lautet, fordert der Viewer-Client den Writer-Client im Raum auf, den vollständigen Editorstatus zu senden.

    /*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. Wenn der Datentyp editorMessage lautet, aktualisiert der Viewer-Client den Editor entsprechend seinem tatsächlichen Inhalt.

    /*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. Implementieren Sie joinRoom() und sendToRoom() mithilfe der APIs von 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
        });
    }
    

Ausführen der Anwendung

Suchen des Repositorys

In den vorherigen Abschnitten wurde die Kernlogik behandelt, die sich auf die Synchronisierung des Editorstatus zwischen den Viewern und dem Writer bezieht. Den vollständigen Code finden Sie in diesem Repository mit Beispielen.

Klonen des Repositorys

Sie können das Repository klonen und npm install ausführen, um Projektabhängigkeiten zu installieren.

Starten des Servers

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

Dies ist die Verbindungszeichenfolge, die Sie in einem vorherigen Schritt erhalten haben.

Testen des Echtzeit-Code-Editors

Öffnen Sie http://localhost:3000 auf einer Browserregisterkarte. Öffnen Sie eine weitere Registerkarte mit der URL, die auf der ersten Webseite angezeigt wird.

Wenn Sie Code auf der ersten Registerkarte schreiben, sollte die Eingabe in Echtzeit auf der anderen Registerkarte angezeigt werden. Web PubSub für Socket.IO erleichtert das Übergeben von Nachrichten in die Cloud. Ihr express-Server stellt nur die statische index.html-Datei und den /negotiate-Endpunkt bereit.