Signatur
Beschreibung
RTCDataChannel ist Teil von WebRTC und bietet einen Kanal für Datenübertragungen direkt zwischen zwei Browsern, ohne Umweg über einen Server. Ein Kanal wird nie direkt über einen Konstruktor erzeugt. Man legt ihn entweder mit RTCPeerConnection.prototype.createDataChannel(label, options) an oder empfängt ihn auf der Gegenseite über das Ereignis datachannel.
Technisch baut der Kanal auf SCTP über DTLS auf. Er ist also immer verschlüsselt und lässt sich flexibel konfigurieren: zuverlässig und geordnet wie TCP (Standard) oder unzuverlässig bzw. ungeordnet wie UDP, über ordered, maxRetransmits oder maxPacketLifeTime. Die zweite Variante eignet sich für Echtzeitspiele, Telemetrie oder Cursor-Positionen, bei denen veraltete Daten wertlos sind.
Wichtige Eigenschaften sind label, readyState ("connecting", "open", "closing", "closed"), bufferedAmount, bufferedAmountLowThreshold, binaryType, id, negotiated, ordered und protocol. Die zentralen Methoden sind send(data) und close(). Die Ereignisse heißen open, message, bufferedamountlow, error, closing und close. Man kann sie per addEventListener oder über die entsprechenden on…-Eigenschaften abonnieren.
Typische Anwendungen sind Dateiübertragungen zwischen Peers, Chats, Multiplayer-Spiele und kollaboratives Editieren. Der Signalisierungsweg (Austausch von SDP und ICE-Kandidaten) muss dabei selbst umgesetzt werden, etwa per WebSocket.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $label Pflicht | string | Parameter von createDataChannel(): frei wählbarer Name des Kanals, auf der Gegenseite über channel.label lesbar. |
|
| $options.ordered | boolean | true | Legt fest, ob Nachrichten in der Sendereihenfolge ankommen müssen. |
| $options.maxRetransmits | number | Maximale Anzahl erneuter Sendeversuche (unzuverlässiger Modus). Darf nicht zusammen mit maxPacketLifeTime gesetzt werden. |
|
| $options.maxPacketLifeTime | number | Zeit in Millisekunden, in der erneute Sendeversuche erlaubt sind (unzuverlässiger Modus). | |
| $options.protocol | string | "" | Name eines Unterprotokolls, z. B. "json". Rein informativ. |
| $options.negotiated | boolean | false | Bei true handeln beide Seiten den Kanal selbst aus: Beide rufen createDataChannel() mit derselben id auf, und es wird kein datachannel-Ereignis ausgelöst. |
| $options.id | number | Stream-ID (0–65534). Wird nur zusammen mit negotiated: true benötigt. |
Rückgabewert
RTCPeerConnection.prototype.createDataChannel() gibt eine neue RTCDataChannel-Instanz zurück, die anfangs den readyState "connecting" hat.Beispiele
Kanal anlegen und auf der Gegenseite empfangen (lokale Demo)
// Zwei Peers im selben Tab, damit die Demo ohne Signalisierungsserver läuft
const pc1 = new RTCPeerConnection();
const pc2 = new RTCPeerConnection();
pc1.onicecandidate = (e) => e.candidate && pc2.addIceCandidate(e.candidate);
pc2.onicecandidate = (e) => e.candidate && pc1.addIceCandidate(e.candidate);
// Sender erzeugt den Kanal
const channel = pc1.createDataChannel('chat', { ordered: true });
channel.addEventListener('open', () => {
console.log('Kanal offen:', channel.label);
channel.send('Hallo Peer!');
});
// Empfänger bekommt den Kanal über das datachannel-Ereignis
pc2.addEventListener('datachannel', ({ channel: remote }) => {
remote.onmessage = (e) => console.log('Empfangen:', e.data);
});
const offer = await pc1.createOffer();
await pc1.setLocalDescription(offer);
await pc2.setRemoteDescription(offer);
const answer = await pc2.createAnswer();
await pc2.setLocalDescription(answer);
await pc1.setRemoteDescription(answer);
Unzuverlässiger Kanal für Echtzeit-Positionsdaten
const pc = new RTCPeerConnection();
// Keine erneuten Sendeversuche, keine Reihenfolgegarantie: wie UDP
const positions = pc.createDataChannel('positions', {
ordered: false,
maxRetransmits: 0
});
positions.onopen = () => {
document.addEventListener('pointermove', (e) => {
if (positions.readyState === 'open') {
positions.send(JSON.stringify({ x: e.clientX, y: e.clientY, t: Date.now() }));
}
});
};
console.log(positions.ordered, positions.maxRetransmits);
Datei in Stücken senden, mit Rückstaukontrolle über bufferedAmount
const CHUNK = 16 * 1024; // 16 KiB funktioniert browserübergreifend zuverlässig
const sendFile = async (channel, file) => {
channel.binaryType = 'arraybuffer';
channel.bufferedAmountLowThreshold = 1024 * 1024; // 1 MiB
const waitForDrain = () =>
new Promise((resolve) =>
channel.addEventListener('bufferedamountlow', resolve, { once: true })
);
for (let offset = 0; offset < file.size; offset += CHUNK) {
if (channel.bufferedAmount > channel.bufferedAmountLowThreshold) {
await waitForDrain(); // Puffer erst leerlaufen lassen
}
const buffer = await file.slice(offset, offset + CHUNK).arrayBuffer();
channel.send(buffer);
}
channel.send(JSON.stringify({ done: true, name: file.name }));
console.log(`${file.name} gesendet (${file.size} Bytes)`);
};
Ausgehandelter Kanal ohne datachannel-Ereignis und sauberes Schließen
// Beide Peers führen diesen Code mit identischer id aus
const pc = new RTCPeerConnection();
const control = pc.createDataChannel('control', { negotiated: true, id: 0 });
const onMessage = (e) => console.log('Steuerbefehl:', e.data);
control.addEventListener('message', onMessage);
control.onclose = () => console.log('Kanal geschlossen');
control.onerror = (e) => console.error('Kanalfehler:', e.error?.message);
// Später: Listener entfernen und Kanal schließen
const shutdown = () => {
control.removeEventListener('message', onMessage);
control.close();
};