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.
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 |
Voraussetzungen
Um alle Schritte in diesem Artikel auszuführen, benötigen Sie:
- 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 Socket.IO-APIs.
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
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 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')));
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 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 Server
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] }
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.
Nachdem ein Client verbunden ist, teilt der Anwendungsserver dem Client mit, dass er angemeldet ist, indem ein benutzerdefiniertes Ereignis mit dem Namen
login
gesendet 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, den ein Client beitreten möchte, verwendensocket.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); });
Nachdem ein Client beigetreten ist, informiert der Server den Client über das erfolgreiche Ergebnis durch Senden eines
message
Ereignisses. Wenn der Client einmessage
Ereignis mit einem TypackJoinRoom
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 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.
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');
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 Namenflush
organisiert die Sendelogik./*client.js*/ socket.on("login", () => { updateStatus('Connected'); joinRoom(socket, `${room_id}`); setInterval(() => flush(), 200); // Update editor content // ... });
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(); }
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
Wie der Writer-Client erstellt der Viewer-Client seinen Socket.IO Client über
initialize()
. Wenn der Viewerclient verbunden ist und einlogin
Ereignis vom Server empfängt, fordert er den Server auf, sich selbst 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 Viewerclient ein
message
Ereignis vom Server empfängt und der Datentyp lautetackJoinRoom
, 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 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; } } });
Implementieren und
sendToRoom()
verwenden SiejoinRoom()
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.