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.
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 |
Voraussetzungen
Damit Sie all den Schritten in diesem Artikel folgen können, benötigen Sie Folgendes:
- Ein Azure-Konto. Wenn Sie kein Azure-Abonnement haben, erstellen Sie ein kostenloses Azure-Konto, bevor Sie beginnen.
- Die Azure CLI (Version 2.29.0 oder höher) oder Azure Cloud Shell zum Verwalten von Azure-Ressourcen.
- Grundlegende Vertrautheit mit APIs von Socket.IO.
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
Erstellen Sie ein Node.js-Projekt:
mkdir codestream cd codestream npm init
Installieren Sie das Server-SDK und Express:
npm install @azure/web-pubsub-socket.io npm install express
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')));
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 einroom
-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
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] }
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.
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"); });
Jeder Client gibt zwei Ereignisse aus, auf die der Server reagieren kann:
joinRoom
undsendToRoom
. Nachdem der Server denroom_id
-Wert abgerufen hat, mit dem ein Client verbunden werden möchte, verwenden Siesocket.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); });
Nachdem ein Client verbunden wurde, informiert der Server den Client über das erfolgreiche Ergebnis, indem ein
message
-Ereignis gesandt wird. Wenn der Client einmessage
-Ereignis mit einem Typ vonackJoinRoom
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'}); } // ... });
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.
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');
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 Namenflush
organisiert die Sendelogik./*client.js*/ socket.on("login", () => { updateStatus('Connected'); joinRoom(socket, `${room_id}`); setInterval(() => flush(), 200); // Update editor content // ... });
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(); }
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
Wie der Writer-Client erstellt der Viewer-Client seinen Socket.IO-Client über
initialize()
. Wenn der Viewer-Client verbunden ist und einlogin
-Ereignis vom Server empfängt, fordert er den Server auf, sich mit dem angegebenen Raum zu verbinden. Die Abfrageroom_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}`); });
Wenn ein Viewer-Client ein
message
-Ereignis vom Server empfängt und der DatentypackJoinRoom
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 //... });
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; } } });
Implementieren Sie
joinRoom()
undsendToRoom()
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.