Share via


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 ihre 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 von Socket.IO-Verbindungen. Mit Web PubSub für Socket.IO müssen Entwickler sich keine Gedanken mehr über das Verwalten persistenter Verbindungen machen.

Überblick

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

  • Monaco Editor, der Code-Editor, der Visual Studio Code unterstützt.
  • Express, ein Node.js-Webframework.
  • APIs, die die Socket.IO-Bibliothek für die Echtzeitkommunikation bereitstellt.
  • Hosten Socket.IO Verbindungen, 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 of the finished code-streaming app.

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

  • Ein Autor, der im Online-Editor eingeben kann und der Inhalt gestreamt wird
  • Viewer, die vom Writer eingegebenen Echtzeitinhalt erhalten und den Inhalt nicht bearbeiten können

Aufbau

Artikel Zweck Vorteile
Socket.IO-Bibliothek Bietet einen mechanismus für bidirektionalen Datenaustausch mit geringer Latenz zwischen der Back-End-Anwendung und Clients. Benutzerfreundliche APIs, die die meisten Echtzeitkommunikationsszenarien abdecken
Web PubSub für Socket.IO Hosten von WebSocket- oder abrufbasierten beständigen Verbindungen mit Socket.IO Clients Unterstützung für 100.000 gleichzeitige Verbindungen; Vereinfachte Anwendungsarchitektur

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

Voraussetzungen

Um alle Schritte in diesem Artikel auszuführen, benötigen Sie:

Erstellen eines 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 einem Verbindungszeichenfolge können Sie eine Verbindung mit Web PubSub für Socket.IO herstellen.

Führen Sie die folgenden Befehle aus: Behalten Sie die zurückgegebene Verbindungszeichenfolge irgendwo bei, 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 für statische 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 eines Endpunkts mit dem Namen /negotiate. Ein Writer-Client trifft 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 Server

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

    /*server.js*/
    const { useAzureSocketIO } = require("@azure/web-pubsub-socket.io");
    
    const wpsOptions = {
        hub: "codestream",
        connectionString: process.argv[2]
    }
    
  2. Erstellen Sie ein 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 persistenter Verbindungen zu einem Azure-Dienst auslagern. Mit Hilfe 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 Socket.IO-APIs kommunizieren. Dieser Prozess wird als Implementierung von Geschäftslogik bezeichnet.

  1. Nachdem ein Client verbunden ist, teilt der Anwendungsserver dem Client mit, dass er angemeldet ist, indem ein benutzerdefiniertes Ereignis mit dem Namen logingesendet 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, den ein Client beitreten möchte, verwenden socket.join Sie die Socket.IO-API, 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 beigetreten ist, informiert der Server den Client über das erfolgreiche Ergebnis durch Senden eines message Ereignisses. Wenn der Client ein message Ereignis mit einem Typ ackJoinRoomempfä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 an den Code-Editor-Zustand 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 fertig sind, können Sie auf clientseitiger Seite arbeiten.

Erste Einrichtung

Sie müssen einen Socket.IO Client für die Kommunikation mit dem Server erstellen. Die Frage ist, 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 zu bedienen.

/*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 organisiert einige Setupvorgänge zusammen:

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

Writer-Client

Wie zuvor Erwähnung haben Sie zwei Benutzerrollen auf der Clientseite: Writer und Viewer. Alles, was der Writer eingibt, wird auf den Bildschirm des Betrachters gestreamt.

  1. Rufen Sie den Endpunkt zum 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 Autor kann antworten, indem er den Server auffordern, sich selbst einem bestimmten Raum anzuschließen. 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 Autor keine Bearbeitungen vornimmt, flush() wird nichts ausgeführt und einfach zurückgegeben. Andernfalls werden die Änderungen am Editorstatus an den Raum gesendet.

    /*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 Zustand des Editors abrufen. Um dies zu erreichen, wird eine Nachricht, die Daten enthält sync , an den Writer-Client gesendet. 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,
            });
        }
    });
    

Viewerclient

  1. Wie der Writer-Client erstellt der Viewer-Client seinen Socket.IO Client über initialize(). Wenn der Viewerclient verbunden ist und ein login Ereignis vom Server empfängt, fordert er den Server auf, sich selbst 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 Viewerclient ein message Ereignis vom Server empfängt und der Datentyp lautet ackJoinRoom, 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 lautet editorMessage, aktualisiert der Viewerclient 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 und sendToRoom() verwenden Sie joinRoom() die 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 Editorzustands zwischen Den Viewern und dem Writer bezieht. Den vollständigen Code finden Sie im Beispiel-Repository.

Klonen des Repositorys

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

Starten des Servers

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

Dies ist die Verbindungszeichenfolge, die Sie in einem früheren Schritt erhalten haben.

Wiedergeben mit dem Echtzeitcode-Editor

Öffnen Sie http://localhost:3000 auf einer Browserregisterkarte. Öffnen Sie eine andere 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 der Cloud. Ihr express Server dient nur der statischen index.html Datei und dem /negotiate Endpunkt.