How RTCDataChannel Sends Data in JavaScript
The RTCDataChannel interface of the WebRTC API enables
bidirectional, low-latency, peer-to-peer transfer of arbitrary
data—including plain text, binary arrays, and raw blobs—directly between
browsers without routing through an intermediary server. This article
explores how RTCDataChannel works under the hood, how the
underlying protocols manage transport, and how to implement it in
JavaScript to send and receive custom data packets.
The Underlying Protocol Stack
Unlike standard media streams in WebRTC that rely on SRTP (Secure
Real-time Transport Protocol), RTCDataChannel uses
SCTP (Stream Control Transmission Protocol)
encapsulated within DTLS (Datagram Transport Layer
Security), which in turn runs over UDP managed by ICE
(Interactive Connectivity Establishment).
- UDP/ICE: Traverses NATs and firewalls to establish the direct direct peer-to-peer connection.
- DTLS: Encrypts all data payload traffic between peers for security.
- SCTP: Manages multiplexing, message delivery guarantees, and packet ordering.
This design gives developers granular control over delivery semantics, allowing the channel to behave reliably like TCP, unreliably like UDP, or in a semi-reliable hybrid mode.
Step 1: Establishing the Peer Connection
Before a data channel can open, two peers must negotiate a connection using an out-of-band signaling mechanism (such as WebSockets) to exchange SDP (Session Description Protocol) offers, answers, and ICE candidates.
const localConnection = new RTCPeerConnection(configuration);Step 2: Creating the Data Channel
One peer initiates the channel by calling
createDataChannel() on its RTCPeerConnection
instance.
// Peer A (Initiator)
const dataChannel = localConnection.createDataChannel("chatChannel", {
ordered: true, // Guarantees message delivery order
// maxRetransmits: 3 // Optional: for unreliable/semi-reliable delivery
});
dataChannel.onopen = () => {
console.log("Data channel is open and ready to transmit.");
};
dataChannel.onclose = () => {
console.log("Data channel closed.");
};The receiving peer listens for the channel creation via the
ondatachannel event:
// Peer B (Receiver)
remoteConnection.ondatachannel = (event) => {
const receiveChannel = event.channel;
receiveChannel.onmessage = (event) => {
console.log("Received data:", event.data);
};
};Step 3: Sending Arbitrary Data Packets
The RTCDataChannel.send() method is capable of
transmitting various data types natively:
Strings (UTF-8 Plain Text / JSON):
dataChannel.send("Hello, Peer!"); dataChannel.send(JSON.stringify({ type: "player_move", x: 10, y: 25 }));ArrayBuffer and TypedArrays (Binary Data):
const buffer = new Uint8Array([0x01, 0xFF, 0x7F, 0x42]); dataChannel.send(buffer);Blobs (Raw File Chunks):
const file = document.querySelector('input[type="file"]').files[0]; dataChannel.send(file);
Step 4: Receiving and Parsing Data
To process binary data correctly on the receiving side, specify the
binaryType property on the channel
('arraybuffer' or 'blob').
receiveChannel.binaryType = "arraybuffer";
receiveChannel.onmessage = (event) => {
if (typeof event.data === "string") {
const message = JSON.parse(event.data);
console.log("Parsed JSON:", message);
} else if (event.data instanceof ArrayBuffer) {
const view = new Uint8Array(event.data);
console.log("Binary packet received:", view);
}
};Managing Backpressure and Buffering
When sending large payloads or rapid streams of packets, data is placed into an internal sending buffer. If packets are queued faster than the network can transmit them, the buffer may overflow.
To prevent packet loss or excessive memory usage:
- Monitor
dataChannel.bufferedAmountto check how many bytes are currently queued. - Set
dataChannel.bufferedAmountLowThresholdand listen for thebufferedamountlowevent before sending the next packet slice.
const CHUNK_SIZE = 16384; // 16 KB
dataChannel.bufferedAmountLowThreshold = 65536; // 64 KB
dataChannel.onbufferedamountlow = () => {
sendNextChunk();
};Reliability and Ordering Configuration
When calling createDataChannel(label, options), the
options object allows customization of delivery
guarantees:
| Property | Type | Description |
|---|---|---|
ordered |
boolean |
Set to false to deliver
packets as soon as they arrive, regardless of sequence. |
maxPacketLifeTime |
number |
The maximum time (in ms) to attempt packet delivery before dropping it. |
maxRetransmits |
number |
The maximum number of retransmission attempts before failing. |
negotiated |
boolean |
If true, avoids out-of-band
channel negotiation if both sides pre-agree on an ID. |
id |
number |
Explicit stream ID (0–65534) used when
negotiated is set to true. |