Mit Backpressure können Sie verhindern, dass Ihre App von WebSocket-Nachrichten überflutet wird oder dass ein WebSocket-Server mit Nachrichten überlastet wird.
Hintergrund
Die WebSocket API bietet eine JavaScript-Schnittstelle für das WebSocket-Protokoll, mit der eine interaktive bidirektionale Kommunikationssitzung zwischen dem Browser des Nutzers und einem Server geöffnet werden kann. Mit dieser API können Sie Nachrichten an einen Server senden und ereignisgesteuerte Antworten erhalten, ohne den Server auf eine Antwort zu befragen.
Streams API
Mit der Streams API können JavaScript-Programme programmatisch auf Streams von Datenchunks zugreifen, die über das Netzwerk empfangen wurden, und sie nach Bedarf verarbeiten. Ein wichtiges Konzept im Zusammenhang mit Streams ist der Backpressure. Dabei wird die Lese- oder Schreibgeschwindigkeit durch einen einzelnen Stream oder eine Pipe-Kette geregelt. Wenn der Stream selbst oder ein Stream weiter unten in der Pipeline noch belegt ist und noch nicht bereit ist, weitere Chunks anzunehmen, wird ein Signal rückwärts durch die Pipeline gesendet, um die Auslieferung entsprechend zu verlangsamen.
Das Problem mit der aktuellen WebSocket API
Es ist nicht möglich, Backpressure auf empfangene Nachrichten anzuwenden.
Bei der aktuellen WebSocket API erfolgt die Reaktion auf eine Nachricht in WebSocket.onmessage
, einem EventHandler
, der aufgerufen wird, wenn eine Nachricht vom Server empfangen wird.
Angenommen, Sie haben eine Anwendung, bei der jedes Mal, wenn eine neue Nachricht empfangen wird, umfangreiche Datenanalysevorgänge ausgeführt werden müssen.
Sie würden den Ablauf wahrscheinlich ähnlich wie im Code unten einrichten. Da Sie das Ergebnis des process()
-Aufrufs await
, sollte alles in Ordnung sein. Stimmt das?
// A heavy data crunching operation.
const process = async (data) => {
return new Promise((resolve) => {
window.setTimeout(() => {
console.log('WebSocket message processed:', data);
return resolve('done');
}, 1000);
});
};
webSocket.onmessage = async (event) => {
const data = event.data;
// Await the result of the processing step in the message handler.
await process(data);
};
Leider falsch! Das Problem mit der aktuellen WebSocket API besteht darin, dass kein Backpressure angewendet werden kann.
Wenn Nachrichten schneller eintreffen, als sie von der process()
-Methode verarbeitet werden können, füllt der Rendering-Prozess entweder den Arbeitsspeicher durch Puffern dieser Nachrichten auf, reagiert nicht mehr, weil die CPU zu 100% ausgelastet ist, oder beides.
Das Anwenden von Backpressure auf gesendete Nachrichten ist nicht ergonomisch
Es ist möglich, Backpressure auf gesendete Nachrichten anzuwenden. Dazu muss jedoch das Attribut WebSocket.bufferedAmount
abgefragt werden, was ineffizient und nicht ergonomisch ist.
Diese schreibgeschützte Eigenschaft gibt die Anzahl der Datenbyte zurück, die über Aufrufe von WebSocket.send()
in die Warteschlange gestellt, aber noch nicht an das Netzwerk übertragen wurden.
Dieser Wert wird auf null zurückgesetzt, sobald alle Daten in der Warteschlange gesendet wurden. Wenn du WebSocket.send()
jedoch weiterhin aufrufst, steigt er weiter an.
Was ist die WebSocketStream API?
Die WebSocketStream API behebt das Problem des nicht vorhandenen oder nicht ergonomischen Backpressure, indem Streams in die WebSocket API eingebunden werden. Das bedeutet, dass Backpressure „kostenlos“ angewendet werden kann, ohne zusätzliche Kosten.
Anwendungsfälle für die WebSocketStream API
Beispiele für Websites, auf denen diese API verwendet werden kann:
- WebSocket-Anwendungen mit hoher Bandbreite, die interaktiv bleiben müssen, insbesondere Video- und Bildschirmfreigabe.
- Ähnlich verhält es sich mit Videoaufzeichnungen und anderen Anwendungen, die im Browser viele Daten generieren, die auf den Server hochgeladen werden müssen. Mit Backpressure kann der Client die Datenerzeugung beenden, anstatt Daten im Arbeitsspeicher anzusammeln.
Aktueller Status
Schritt | Status |
---|---|
1. Erläuternde Mitteilung erstellen | Abschließen |
2. Ersten Entwurf der Spezifikation erstellen | In Bearbeitung |
3. Feedback einholen und Design iterieren | In Bearbeitung |
4. Ursprungstest | Abschließen |
5. Starten | Nicht gestartet |
WebSocketStream API verwenden
Die WebSocketStream API basiert auf Promises, was die Verwendung in einer modernen JavaScript-Umgebung natürlich macht.
Erstellen Sie zuerst eine neue WebSocketStream
und übergeben Sie ihr die URL des WebSocket-Servers.
Als Nächstes warten Sie, bis die Verbindung opened
ist, was zu einer ReadableStream
und/oder einer WritableStream
führt.
Durch Aufrufen der Methode ReadableStream.getReader()
erhalten Sie schließlich ein ReadableStreamDefaultReader
, aus dem Sie dann read()
-Daten abrufen können, bis der Stream beendet ist, d. h. bis ein Objekt vom Typ {value: undefined, done: true}
zurückgegeben wird.
Wenn Sie also die Methode WritableStream.getWriter()
aufrufen, erhalten Sie eine WritableStreamDefaultWriter
, auf die Sie dann Daten write()
schreiben können.
const wss = new WebSocketStream(WSS_URL);
const {readable, writable} = await wss.opened;
const reader = readable.getReader();
const writer = writable.getWriter();
while (true) {
const {value, done} = await reader.read();
if (done) {
break;
}
const result = await process(value);
await writer.write(result);
}
Gegendruck
Was ist mit der versprochenen Gegendruckfunktion?
Sie erhalten die Funktion kostenlos und müssen nichts weiter tun.
Wenn process()
mehr Zeit in Anspruch nimmt, wird die nächste Nachricht erst verarbeitet, wenn die Pipeline bereit ist.
Ebenso wird der Schritt WritableStreamDefaultWriter.write()
nur ausgeführt, wenn dies sicher möglich ist.
Erweiterte Beispiele
Das zweite Argument für WebSocketStream ist eine Options-Bag, die eine zukünftige Erweiterung ermöglicht.
Die einzige Option ist protocols
, die sich genauso verhält wie das zweite Argument des WebSocket-Konstruktors:
const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.opened;
Die ausgewählten protocol
und potenziellen extensions
sind Teil des Wörterbuchs, das über das WebSocketStream.opened
-Versprechen verfügbar ist.
Alle Informationen zur Live-Verbindung werden über dieses Versprechen bereitgestellt, da es nicht relevant ist, ob die Verbindung fehlschlägt.
const {readable, writable, protocol, extensions} = await chatWSS.opened;
Informationen zur geschlossenen WebSocketStream-Verbindung
Die Informationen, die über die Ereignisse WebSocket.onclose
und WebSocket.onerror
in der WebSocket API verfügbar waren, sind jetzt über das WebSocketStream.closed
-Versprechen verfügbar.
Das Versprechen wird bei einem nicht korrekten Schließen abgelehnt. Andernfalls wird der vom Server gesendete Code und Grund zurückgegeben.
Alle möglichen Statuscodes und ihre Bedeutung werden in der Liste der CloseEvent
Statuscodes erläutert.
const {code, reason} = await chatWSS.closed;
WebSocketStream-Verbindung schließen
Ein WebSocketStream kann mit einem AbortController
geschlossen werden.
Übergeben Sie daher einen AbortSignal
an den WebSocketStream
-Konstruktor.
const controller = new AbortController();
const wss = new WebSocketStream(URL, {signal: controller.signal});
setTimeout(() => controller.abort(), 1000);
Alternativ können Sie auch die Methode WebSocketStream.close()
verwenden. Ihr Hauptzweck besteht jedoch darin, den Code und den Grund anzugeben, der an den Server gesendet wird.
wss.close({code: 4000, reason: 'Game over'});
Progressive Verbesserung und Interoperabilität
Chrome ist derzeit der einzige Browser, der die WebSocketStream API implementiert.
Für die Interoperabilität mit der klassischen WebSocket API ist es nicht möglich, Backpressure auf empfangene Nachrichten anzuwenden.
Es ist möglich, Backpressure auf gesendete Nachrichten anzuwenden. Dazu muss jedoch das Attribut WebSocket.bufferedAmount
abgefragt werden, was ineffizient und nicht ergonomisch ist.
Funktionserkennung
So prüfen Sie, ob die WebSocketStream API unterstützt wird:
if ('WebSocketStream' in window) {
// `WebSocketStream` is supported!
}
Demo
In unterstützten Browsern kannst du die WebSocketStream API im eingebetteten Iframe oder direkt auf Glitch in Aktion sehen.
Feedback
Das Chrome-Team möchte mehr über Ihre Erfahrungen mit der WebSocketStream API erfahren.
Informationen zum API-Design
Funktioniert die API nicht wie erwartet? Oder fehlen Methoden oder Eigenschaften, die Sie für die Implementierung Ihrer Idee benötigen? Haben Sie Fragen oder Kommentare zum Sicherheitsmodell? Reichen Sie ein Problem mit der Spezifikation im entsprechenden GitHub-Repository ein oder kommentieren Sie ein vorhandenes Problem.
Problem mit der Implementierung melden
Haben Sie einen Fehler in der Chrome-Implementierung gefunden?
Oder unterscheidet sich die Implementierung von der Spezifikation?
Melden Sie den Fehler unter new.crbug.com. Geben Sie dabei so viele Details wie möglich an, eine einfache Anleitung zur Reproduktion und geben Sie Blink>Network>WebSockets
in das Feld Components ein.
Glitch eignet sich hervorragend, um schnell und einfach reproduzierbare Fälle zu teilen.
Unterstützung für die API anzeigen
Beabsichtigen Sie, die WebSocketStream API zu verwenden? Ihre öffentliche Unterstützung hilft dem Chrome-Team, Funktionen zu priorisieren, und zeigt anderen Browseranbietern, wie wichtig es ist, sie zu unterstützen.
Senden Sie einen Tweet an @ChromiumDev mit dem Hashtag #WebSocketStream
und teilen Sie uns mit, wo und wie Sie ihn verwenden.
Nützliche Links
- Öffentliche Erläuterung
- WebSocketStream API-Demo | WebSocketStream API-Demoquelle
- Tracking-Fehler
- Eintrag in ChromeStatus.com
- Blink-Komponente:
Blink>Network>WebSockets
Danksagungen
Die WebSocketStream API wurde von Adam Rice und Yutaka Hirano implementiert.