Send data between browsers with WebRTC data channels

Sending data between two browsers for communication, gaming, or file transfer can be a rather involved process. It requires setting up and paying for a server to relay data, and perhaps scaling this to multiple data centers. In this scenario, there is potential for high latency and it's difficult to keep data private.

These problems can be alleviated by using WebRTC's RTCDataChannel API to transfer data directly from one peer to another. This article covers the basics of how to set up and use data channels, as well as the common use cases on the web today.

Why another data channel?

We have WebSocket, AJAX and Server Sent Events. Why do we need another communication channel? WebSocket is bidirectional, but all these technologies are designed for communication to or from a server.

RTCDataChannel takes a different approach:

  • It works with the RTCPeerConnection API, which enables peer-to-peer connectivity. This can result in lower latency - no intermediary server and fewer 'hops'.
  • RTCDataChannel uses Stream Control Transmission Protocol (SCTP), allowing configurable delivery semantics-out-of-order delivery and retransmit configuration.

RTCDataChannel is available now with SCTP support on desktop and Android in Google Chrome, Opera, and Firefox.

A caveat: Signaling, STUN, and TURN

WebRTC enables peer-to-peer communication, but it still needs servers for signaling to exchange media and network metadata to bootstrap a peer connection.

WebRTC copes with NATs and firewalls with:

  • The ICE framework to establish the best possible network path between peers.
  • STUN servers to ascertain a publicly accessible IP and port for each peer.
  • TURN servers if direct connection fails and data relaying is required.

For more information about how WebRTC works with servers for signaling and networking, see WebRTC inthe real world: STUN, TURN, and signaling.

The capabilities

The RTCDataChannel API supports a flexible set of data types. The API is designed to mimic WebSocket exactly, and RTCDataChannel supports strings as well as some of the binary types in JavaScript, such as Blob, ArrayBuffer, and ArrayBufferView. These types can be helpful when working with file transfer and multiplayer gaming.

RTCDataChannel can work in unreliable and unordered mode (analogous to User Datagram Protocol or UDP), reliable and ordered mode (analogous to Transmission Control Protocol or TCP), and partial reliable modes:

  • Reliable and ordered mode guarantees the transmission of messages and also the order in which they are delivered. This takes extra overhead, thus potentially making this mode slower.
  • Unreliable and unordered mode does not guarantee every message gets to the other side nor what order they get there in. This removes the overhead, allowing this mode to work much faster.
  • Partial reliable mode guarantees the transmission of message under a specific condition, such as a retransmit timeout or a maximum amount of retransmissions. The ordering of messages is also configurable.

Performance for the first two modes is about the same when there are no packet losses. However, in reliable and ordered mode, a lost packet causes other packets to get blocked behind it, and the lost packet might be stale by the time that it is retransmitted and arrives. It is, of course, possible to use multiple data channels within the same app, each with their own reliable or unreliable semantics.

Here's a helpful table from High Performance Browser Networking by Ilya Grigorik:

TCPUDPSCTP
ReliabilityReliableUnreliableConfigurable
DeliveryOrderedUnorderedConfigurable
TransmissionByte-orientedMessage-orientedMessage-oriented
Flow controlYesNoYes
Congestion controlYesNoYes

Next, you learn how to configure RTCDataChannel to use reliable and ordered or unreliable and unordered mode.

Configuring data channels

There are several simple demos of RTCDataChannel online:

In these examples, the browser makes a peer connection to itself, then creates a data channel and sends a message through the peer connection. It is then creating a data channel and sending the message along the peer connection. Finally, your message appears in the box on the other side of the page!

The code to get started with this is short:

const peerConnection = new RTCPeerConnection();

// Establish your peer connection using your signaling channel here
const dataChannel =
  peerConnection.createDataChannel("myLabel", dataChannelOptions);

dataChannel.onerror = (error) => {
  console.log("Data Channel Error:", error);
};

dataChannel.onmessage = (event) => {
  console.log("Got Data Channel Message:", event.data);
};

dataChannel.onopen = () => {
  dataChannel.send("Hello World!");
};

dataChannel.onclose = () => {
  console.log("The Data Channel is Closed");
};

The dataChannel object is created from an already-established peer connection. It can be created before or after signaling happens. You then pass in a label to distinguish this channel from others and a set of optional configuration settings:

const dataChannelOptions = {
  ordered: false, // do not guarantee order
  maxPacketLifeTime: 3000, // in milliseconds
};

It is also possible to add a maxRetransmits option (the number of times to try before failing), but you can only specify maxRetransmits or maxPacketLifeTime, not both. For UDP semantics, set maxRetransmits to 0 and ordered to false. For more information, see these IETF RFCs: Stream Control Transmission Protocol and Stream Control Transmission Protocol Partial Reliability Extension.

  • ordered: if the data channel should guarantee order or not
  • maxPacketLifeTime: the maximum time to try and retransmit a failed message
  • maxRetransmits: the maximum number of times to try and retransmit a failed message
  • protocol: allows a subprotocol to be used, which provides meta information toward the app
  • negotiated: if set to true, removes the automatic setting up of a data channel on the other peer, providing your own way to create a data channel with the same ID on the other side
  • id: allows you to provide your own ID for the channel that can only be used in combination with negotiated set to true)

The only options that most people need to use are the first three: ordered, maxPacketLifeTime, and maxRetransmits. With SCTP (now used by all browsers that support WebRTC) reliable and ordered is true by default. It makes sense to use unreliable and unordered if you want full control from the app layer, but in most cases, partial reliability is helpful.

Note that, as with WebSocket, RTCDataChannel fires events when a connection is established, closed, or errors, and when it receives a message from the other peer.

Is it safe?

Encryption is mandatory for all WebRTC components. With RTCDataChannel, all data is secured with Datagram Transport Layer Security (DTLS). DTLS is a derivative of SSL, meaning your data will be as secure as using any standard SSL-based connection. DTLS is standardized and built into all browsers that support WebRTC. For more information, see Wireshark wiki.

Change how you think about data

Handling large amounts of data can be a pain point in JavaScript. As the developers of Sharefest pointed out, this required thinking about data in a new way. If you are transferring a file that is larger than the amount of memory you have available, you have to think about new ways to save this information. This is where technologies, such as the FileSystem API, come into play, as you see next.

Build a file-sharing app

Creating a web app that can share files in the browser is now possible with RTCDataChannel. Building on top of RTCDataChannel means that the transferred file data is encrypted and does not touch an app provider's servers. This functionality, combined with the possibility of connecting to multiple clients for faster sharing, makes WebRTC file sharing a strong candidate for the web.

Several steps are required to make a successful transfer:

  1. Read a file in JavaScript using the File API.
  2. Make a peer connection between clients with RTCPeerConnection.
  3. Create a data channel between clients with RTCDataChannel.

There are several points to consider when trying to send files over RTCDataChannel:

  • File size: if file size is reasonably small and can be stored and loaded as one Blob, you can load into memory using the File API and then send the file over a reliable channel as is (though bear in mind that browsers impose limits on maximum transfer size). As file size gets larger, things get trickier. When a chunking mechanism is required, file chunks are loaded and sent to another peer, accompanied with chunkID metadata so the peer can recognize them. Note that, in this case, you also need to save the chunks first to offline storage (for example, using the FileSystem API) and save it to the user's disk only when you have the file in its entirety.
  • Chunk size: these are the smallest "atoms" of data for your app. Chunking is required because there is currently a send size limit (though this will be fixed in a future version of data channels). The current recommendation for maximum chunk size is 64KiB.

Once the file is fully transferred to the other side, it can be downloaded using an anchor tag:

function saveFile(blob) {
  const link = document.createElement('a');
  link.href = window.URL.createObjectURL(blob);
  link.download = 'File Name';
  link.click();
};

These file-sharing apps on PubShare and GitHub use this technique. They are both open source and provide a good foundation for a file-sharing app based on RTCDataChannel.

So what can you do?

RTCDataChannel opens the doors to new ways to build apps for file sharing, multiplayer gaming, and content delivery.

  • Peer-to-peer file sharing as previously described
  • Multiplayer gaming, paired with other technologies, such as WebGL, as seen in Mozilla's BananaBread
  • Content delivery as being reinvented by PeerCDN, a framework that delivers web assets through peer-to-peer data communication

Change the way you build apps

You can now provide more-engaging apps by using high-performance, low-latency connections through RTCDataChannel. Frameworks, such as PeerJS and the PubNub WebRTC SDK, make RTCDataChannel easier to implement and the API now has wide support across platforms.

The advent of RTCDataChannel can change the way that you think about data transfer in the browser.

Find out more