Standard-Webanwendungen sind in der Regel auf bestimmte Kommunikationsprotokolle wie HTTP und APIs wie WebSocket und WebRTC beschränkt. Sie sind zwar leistungsstark, aber so konzipiert, dass sie stark eingeschränkt sind, um Missbrauch zu verhindern. Sie können keine Raw-TCP- oder -UDP-Verbindungen herstellen, was die Möglichkeit von Web-Apps einschränkt, mit Legacy-Systemen oder Hardwaregeräten zu kommunizieren, die eigene Nicht-Web-Protokolle verwenden. Sie möchten beispielsweise einen webbasierten SSH-Client erstellen, eine Verbindung zu einem lokalen Drucker herstellen oder eine Flotte von IoT-Geräten verwalten. Bisher waren dafür Browser-Plug-ins oder native Hilfsanwendungen erforderlich.
Die Direct Sockets API behebt diese Einschränkung, indem sie es isolierten Web-Apps (IWAs) ermöglicht, direkte TCP- und UDP-Verbindungen ohne Relais-Server herzustellen. Dank zusätzlicher Sicherheitsmaßnahmen wie einer strengen Content Security Policy (CSP) und der Cross-Origin-Isolation kann diese API sicher bereitgestellt werden.
Anwendungsfälle
Wann sollten Sie Direct Sockets anstelle von Standard-WebSockets verwenden?
- IoT- und Smart-Home-Geräte:Kommunikation mit Hardware, die rohes TCP/UDP anstelle von HTTP verwendet.
- Alte Systeme:Verbindung zu älteren Mailservern (SMTP/IMAP), IRC-Chatservern oder Druckern.
- Remotedesktop und Terminals:Implementieren von SSH-, Telnet- oder RDP-Clients.
- P2P-Systeme:Implementierung von Distributed Hash Tables (DHT) oder robusten Tools für die Zusammenarbeit (z. B. IPFS).
- Media-Broadcasting:UDP wird verwendet, um Inhalte gleichzeitig an mehrere Endpunkte zu streamen (Multicasting). Dies ermöglicht Anwendungsfälle wie die koordinierte Videowiedergabe in einem Netzwerk von Einzelhandelskiosken.
- Server- und Listener-Funktionen:Konfigurieren Sie die IWA so, dass sie als Empfangsendpunkt für eingehende TCP-Verbindungen oder UDP-Datagramme mit
TCPServerSocketoder gebundenemUDPSocketfungiert.
Voraussetzungen für Direct Sockets
Bevor Sie Direct Sockets verwenden können, müssen Sie eine funktionierende IWA einrichten. Anschließend können Sie Direct Sockets in Ihre Seiten einbinden.
Berechtigungsrichtlinie hinzufügen
Wenn Sie Direct Sockets verwenden möchten, müssen Sie das permissions_policy-Objekt in Ihrem IWA-Manifest konfigurieren. Sie müssen den Schlüssel direct-sockets hinzufügen, um die API explizit zu aktivieren. Außerdem müssen Sie den Schlüssel cross-origin-isolated angeben. Dieser Schlüssel ist nicht spezifisch für Direct Sockets, sondern für alle IWAs erforderlich. Er bestimmt, ob das Dokument auf APIs zugreifen kann, die eine ursprungsübergreifende Isolation erfordern.
{
"permissions_policy": {
"direct-sockets": ["self"],
"cross-origin-isolated": ["self"]
}
}
Der Schlüssel „direct-sockets“ bestimmt, ob Aufrufe von new TCPSocket(...), new
TCPServerSocket(...) oder new UDPSocket(...) zulässig sind. Wenn diese Richtlinie nicht festgelegt ist, wird für diese Konstruktoren sofort eine NotAllowedError zurückgegeben.
TCPSocket implementieren
Anwendungen können eine TCP-Verbindung anfordern, indem sie eine TCPSocket-Instanz erstellen.
Verbindung öffnen
Verwenden Sie den Operator new und await, um eine Verbindung zu öffnen.
Der TCPSocket-Konstruktor initiiert die Verbindung mit den angegebenen remoteAddress und remotePort.
const remoteAddress = 'example.com';
const remotePort = 7;
// Configure options like keepAlive or buffering
const options = {
keepAlive: true,
keepAliveDelay: 720000
};
let tcpSocket = new TCPSocket(remoteAddress, remotePort, options);
// Wait for the connection to be established
let { readable, writable } = await tcpSocket.opened;
Das optionale Konfigurationsobjekt ermöglicht eine detaillierte Netzwerksteuerung. In diesem speziellen Fall ist keepAliveDelay auf 720.000 Millisekunden festgelegt, um die Verbindung während Inaktivitätsphasen aufrechtzuerhalten. Entwickler können hier auch andere Eigenschaften konfigurieren, z. B. noDelay, wodurch der Nagle-Algorithmus deaktiviert wird, um zu verhindern, dass das System kleine Pakete zusammenfasst. Dadurch kann die Latenz verringert werden. Mit sendBufferSize und receiveBufferSize lässt sich der Durchsatz verwalten.
Im letzten Teil des vorherigen Snippets wartet der Code auf das geöffnete Promise, das erst aufgelöst wird, wenn der Handshake abgeschlossen ist. Es wird ein TCPSocketOpenInfo-Objekt zurückgegeben, das die für die Datenübertragung erforderlichen lesbaren und beschreibbaren Streams enthält.
Lese- und Schreibzugriff
Sobald der Socket geöffnet ist, können Sie über die Standard-Streams API-Schnittstellen mit ihm interagieren.
- Schreiben:Der beschreibbare Stream akzeptiert ein
BufferSource(wie einArrayBuffer). - Lesen:Der lesbare Stream liefert
Uint8Array-Daten.
// Writing data
const writer = writable.getWriter();
const encoder = new TextEncoder();
await writer.write(encoder.encode("Hello Server"));
// Call when done
writer.releaseLock();
// Reading data
const reader = readable.getReader();
const { value, done } = await reader.read();
if (!done) {
const decoder = new TextDecoder();
console.log("Received:", decoder.decode(value));
}
// Call when done
reader.releaseLock();
Optimiertes Lesen mit BYOB
Für leistungsstarke Anwendungen, bei denen die Verwaltung der Speicherzuweisung entscheidend ist, unterstützt die API das Lesen mit „Bring Your Own Buffer“ (BYOB). Anstatt den Browser für jeden empfangenen Datenblock einen neuen Puffer zuweisen zu lassen, können Sie dem Reader einen vorab zugewiesenen Puffer übergeben. Dadurch wird der Overhead für die automatische Speicherbereinigung reduziert, da Daten direkt in den vorhandenen Arbeitsspeicher geschrieben werden.
// 1. Get a BYOB reader explicitly
const reader = readable.getReader({ mode: 'byob' });
// 2. Allocate a reusable buffer (e.g., 4KB)
let buffer = new Uint8Array(4096);
// 3. Read directly into the existing buffer
const { value, done } = await reader.read(buffer);
if (!done) {
// 'value' is a view of the data written directly into your buffer
console.log("Bytes received:", value.byteLength);
}
reader.releaseLock();
UDPSocket implementieren
Die Klasse UDPSocket ermöglicht die UDP-Kommunikation. Die Funktion wird in zwei verschiedenen Modi ausgeführt, je nachdem, wie Sie die Optionen konfigurieren.
Verbundener Modus
In diesem Modus kommuniziert der Socket mit einem einzelnen bestimmten Ziel. Das ist nützlich für Standard-Client-Server-Aufgaben.
// Connect to a specific remote host
let udpSocket = new UDPSocket({
remoteAddress: 'example.com',
remotePort: 7 });
let { readable, writable } = await udpSocket.opened;
Gebundener Modus
In diesem Modus wird der Socket an einen lokalen IP-Endpunkt gebunden. Es kann Datagramme von beliebigen Quellen empfangen und an beliebige Ziele senden. Dies wird häufig für lokale Erkennungsprotokolle oder serverähnliches Verhalten verwendet.
// Bind to all interfaces (IPv6)
let udpSocket = new UDPSocket({
localAddress: '::'
// omitting localPort lets the OS pick one
});
// localPort will tell you the OS-selected port.
let { readable, writable, localPort } = await udpSocket.opened;
UDP-Nachrichten verarbeiten
Im Gegensatz zum TCP-Byte-Stream werden bei UDP-Streams UDPMessage-Objekte verwendet, die die Daten und die Informationen zur Remoteadresse enthalten. Im folgenden Code wird gezeigt, wie Ein-/Ausgabeoperationen bei Verwendung eines UDPSocket im „gebundenen Modus“ behandelt werden.
// Writing (Bound Mode requires specifying destination)
const writer = writable.getWriter();
await writer.write({
data: new TextEncoder().encode("Ping"),
remoteAddress: '192.168.1.50',
remotePort: 8080
});
// Reading
const reader = readable.getReader();
const { value } = await reader.read();
// value contains: { data, remoteAddress, remotePort }
console.log(`Received from ${value.remoteAddress}:`, value.data);
Im Gegensatz zum „Verbindungsmodus“, in dem der Socket an einen bestimmten Peer gebunden ist, kann der Socket im gebundenen Modus mit beliebigen Zielen kommunizieren. Wenn Sie Daten in den beschreibbaren Stream schreiben, müssen Sie daher ein UDPMessage-Objekt übergeben, das die remoteAddress und remotePort für jedes Paket explizit angibt. So wird dem Socket genau mitgeteilt, wohin das jeweilige Datagramm weitergeleitet werden soll. Wenn Sie aus dem lesbaren Stream lesen, enthält der zurückgegebene Wert nicht nur die Daten-Payload, sondern auch die remoteAddress und remotePort des Absenders. So kann Ihre Anwendung den Ursprung jedes eingehenden Pakets ermitteln.
Hinweis:Wenn Sie UDPSocket im „verbundenen Modus“ verwenden, ist der Socket effektiv an einen bestimmten Peer gebunden, was den E/A-Prozess vereinfacht. In diesem Modus sind die Attribute remoteAddress und remotePort beim Schreiben effektiv No-Ops, da das Ziel bereits festgelegt ist. Beim Lesen von Nachrichten geben diese Eigenschaften ebenfalls „null“ zurück, da die Quelle garantiert der verbundene Peer ist.
Unterstützung von Multicast
Für Anwendungsfälle wie die Synchronisierung der Videowiedergabe auf mehreren Kiosken oder die Implementierung der lokalen Geräteerkennung (z. B. mDNS) unterstützt Direct Sockets Multicast-UDP. So können Nachrichten an eine „Gruppen“-Adresse gesendet und von allen Abonnenten im Netzwerk empfangen werden, anstatt nur von einem bestimmten Peer.
Multicast-Berechtigungen
Wenn Sie Multicast-Funktionen verwenden möchten, müssen Sie Ihrem IWA-Manifest die entsprechende direct-sockets-multicast-Berechtigung hinzufügen. Diese Berechtigung unterscheidet sich von der standardmäßigen Berechtigung für direkte Sockets und ist erforderlich, da Multicast nur in privaten Netzwerken verwendet wird.
{
"permissions_policy": {
"direct-sockets": ["self"],
"direct-sockets-multicast": ["self"],
"direct-sockets-private": ["self"],
"cross-origin-isolated": ["self"]
}
}
Multicast-Datagramme senden
Das Senden an eine Multicastgruppe ähnelt dem standardmäßigen UDP-Verbindungsmodus, mit dem Unterschied, dass es zusätzliche Optionen zur Steuerung des Paketverhaltens gibt.
const MULTICAST_GROUP = '239.0.0.1';
const PORT = 12345;
const socket = new UDPSocket({
remoteAddress: MULTICAST_GROUP,
remotePort: PORT,
// Time To Live: How many router hops the packet can survive (default: 1)
multicastTimeToLive: 5,
// Loopback: Whether to receive your own packets (default: true)
multicastLoopback: true
});
const { writable } = await socket.opened;
// Write to the stream as usual...
Multicast-Datagramme empfangen
Wenn Sie Multicast-Traffic empfangen möchten, müssen Sie einen UDPSocket im „gebundenen Modus“ öffnen (in der Regel wird an 0.0.0.0 oder :: gebunden) und dann einer bestimmten Gruppe mit MulticastController beitreten. Sie können auch die Option multicastAllowAddressSharing verwenden (ähnlich wie SO_REUSEADDR unter Unix). Dies ist für Protokolle zur Geräteerkennung unerlässlich, bei denen mehrere Anwendungen auf demselben Gerät denselben Port überwachen müssen.
const socket = new UDPSocket({
localAddress: '0.0.0.0', // Listen on all interfaces
localPort: 12345,
multicastAllowAddressSharing: true // Allow multiple applications to bind to the same address / port pair.
});
// The open info contains the MulticastController
const { readable, multicastController } = await socket.opened;
// Join the group to start receiving packets
await multicastController.joinGroup('239.0.0.1');
const reader = readable.getReader();
// Read the stream...
const { value } = await reader.read();
console.log(`Received multicast from ${value.remoteAddress}`);
// When finished, you can leave the group (this is an optional, but recommended practice)
await multicastController.leaveGroup('239.0.0.1');
Server erstellen
Die API unterstützt auch TCPServerSocket zum Akzeptieren eingehender TCP-Verbindungen, sodass Ihre IWA als lokaler Server fungieren kann. Der folgende Code veranschaulicht, wie ein TCP-Server über die TCPServerSocket-Schnittstelle eingerichtet wird.
// Listen on all interfaces (IPv6)
let tcpServerSocket = new TCPServerSocket('::');
// Accept connections via the readable stream
let { readable } = await tcpServerSocket.opened;
let reader = readable.getReader();
// Wait for a client to connect
let { value: clientSocket } = await reader.read();
// 'clientSocket' is a standard TCPSocket you can now read/write to
Durch die Instanziierung der Klasse mit der Adresse '::' wird der Server an alle verfügbaren IPv6-Netzwerkschnittstellen gebunden, um auf eingehende Versuche zu warten. Im Gegensatz zu herkömmlichen Callback-basierten Server-APIs wird bei dieser API das Streams API-Muster des Webs verwendet: Eingehende Verbindungen werden als ReadableStream bereitgestellt. Wenn Sie reader.read() aufrufen, wartet die Anwendung auf die nächste Verbindung aus der Warteschlange und akzeptiert sie. Der Aufruf wird zu einem Wert aufgelöst, der eine voll funktionsfähige TCPSocket-Instanz ist, die für die bidirektionale Kommunikation mit dem jeweiligen Client bereit ist.
Direct Sockets mit den Chrome-Entwicklertools debuggen
Ab Chrome 138 können Sie Direct Sockets-Traffic direkt im Bereich Netzwerk in den Chrome-Entwicklertools debuggen. Externe Packet Sniffer sind nicht mehr erforderlich. Mit diesen Tools können Sie TCPSocket-Verbindungen sowie UDPSocket-Traffic (im gebundenen und verbundenen Modus) zusammen mit Ihren Standard-HTTP-Anfragen überwachen.
So prüfen Sie die Netzwerkaktivität Ihrer App:
- Öffnen Sie in den Chrome-Entwicklertools den Bereich Netzwerk.
- Suchen Sie in der Anfragetabelle nach der Socket-Verbindung und wählen Sie sie aus.
- Öffnen Sie den Tab Nachrichten, um ein Log aller übertragenen und empfangenen Daten aufzurufen.

Diese Ansicht enthält einen Hex-Viewer, mit dem Sie die binäre Rohnutzlast Ihrer TCP- und UDP-Nachrichten untersuchen können, um sicherzustellen, dass Ihre Protokollimplementierung bytegenau ist.
Demo
IWA Kitchen Sink ist eine App mit mehreren Tabs, auf denen jeweils eine andere IWA-API wie Direct Sockets oder Controlled Frame demonstriert wird.
Alternativ enthält die Telnet-Client-Demo eine isolierte Web-App, mit der der Nutzer über ein interaktives Terminal eine Verbindung zu einem TCP/IP-Server herstellen kann. Mit anderen Worten: ein Telnet-Client.
Fazit
Die Direct Sockets API schließt eine kritische Funktionslücke, indem sie es Webanwendungen ermöglicht, rohe Netzwerkprotokolle zu verarbeiten, die zuvor ohne native Wrapper nicht unterstützt werden konnten. Es geht über die einfache Clientverbindung hinaus: Mit TCPServerSocket können Anwendungen auf eingehende Verbindungen warten, während UDPSocket flexible Modi für die Peer-to-Peer-Kommunikation und die Erkennung im lokalen Netzwerk bietet.
Durch die Bereitstellung dieser Roh-TCP- und ‑UDP-Funktionen über die moderne Streams API können Sie jetzt vollwertige Implementierungen von Legacy-Protokollen wie SSH, RDP oder benutzerdefinierten IoT-Standards direkt in JavaScript erstellen. Da diese API einen Netzwerkzugriff auf niedriger Ebene gewährt, birgt sie erhebliche Sicherheitsrisiken. Daher ist sie auf isolierte Web-Apps (IWAs) beschränkt. So wird sichergestellt, dass diese Funktion nur vertrauenswürdigen, explizit installierten Anwendungen gewährt wird, die strenge Sicherheitsrichtlinien durchsetzen. So können Sie leistungsstarke, gerätezentrierte Anwendungen entwickeln und gleichzeitig die Sicherheit gewährleisten, die Nutzer von der Webplattform erwarten.