
Many have heard about the project
WebRTC , so I will not delve into the description. The other day I wanted to try sending messages between browsers, and to figure this out, I decided to write a primitive P2P chat. The experiment was deleted, and based on the reasons I decided to write this post. On Habré there were already articles covering the use of WebRTC for video transmission, but I was interested in the possibility of exchanging text or binary data for the first (and last) turn.
For communication between clients, we will use RTCPeerConnection (to establish a connection) and RTCDataChannel (for data transfer). In the process, we will also need RTCIceCandidate and RTCSessionDescription, but more on that later.
DataChannel protocol support has recently appeared in browsers, so for all this to work, you need Firefox 19+ or Chrome 25+. However, in Firefox, <22 WebRTC is disabled by default (the media.peerconnection.enabled parameter is set to true), and Chrome 25 needs to be launched with the - enable-data-channels flag. I did not look at them, and this post is focused on Firefox 22+ and Chrome 26+. Opera 15 does not support WebRTC.
')
Go
Since all this is in development and the designers in Firefox and Chrome have the prefixes moz and webkit respectively, let's put things in order:
window._RTCPeerConnection = window.webkitPeerConnection00 || window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.RTCPeerConnection || window.PeerConnection; window._RTCIceCandidate = window.webkitRTCIceCandidate || window.mozRTCIceCandidate || window.RTCIceCandidate; window._RTCSessionDescription = window.webkitRTCSessionDescription || window.mozRTCSessionDescription || window.RTCSessionDescription;
Implementations in Firefox and Chrome today are different, so we will need to define a browser.
var browser = { mozilla: /firefox/i.test(navigator.userAgent), chrome: /chrom(e|ium)/i.test(navigator.userAgent) };
In order for clients to know about each other, it is proposed to use a “signaling” mechanism, the implementation of which WebRTC leaves to the developer. Usually this is a server through which clients learn about each other and exchange information, after which they establish a direct connection. This diagram is well illustrated in the illustration I borrowed
from here :

- the first client sends an Offer to the second client via the server;
- the second client sends the Answer to the first via the server;
- customers now know about each other and can connect.
In my case, I used WebSocket to communicate with the server on node.js. If this is a chat, then our server remembers every connected client, knows how to transfer data from the client to the client and return a list of connected clients (for newly arrived users).
Here I will not give the server code, since this is beyond the scope of the article. Let the client-side communication interface with our server be:
var observer = {
Making a connection
Imagine that there are two users - Alice and Bob. Bob went into the chat when there was no one there, and Alice went in a minute later and found out from the signal server that Bob was online and waiting for her. In this case, Alice will send a connection request to Bob, and Bob will respond to her.
To start the connection, Alice creates an RTCPeerConnection object (as you remember, we did a cross-browser _RTCPeerConnection just above). The constructor needs to pass two arguments with parameters, which I will discuss below.
var pc, channel; var config = { iceServers: [{ url: !browser.mozilla ? "stun:stun.l.google.com:19302" : "stun:23.21.150.121" }] }; var constrains = { options: [{ DtlsSrtpKeyAgreement: true }, { RtpDataChannels: true }] }; function createPC(isOffer) { pc = new _RTCPeerConnection(config, constrains);
Since in the real world many users are behind provider
NAT , WebRTC provides ways to bypass it (
ICE ,
STUN ,
TURN ). The first parameter of the
config is an object with an array of STUN and / or TURN servers. You can use public, you can raise your own. I used the STUN server from google. By the way, if I understand correctly, today Firefox has problems using domain STUN servers, so it recommends using others.
The
constrains parameter is optional; it passes the connection settings. About the option
DtlsSrtpKeyAgreement can be read
here , and the option
RtpDataChannels ,
apparently , is needed for Chrome 25 (and, perhaps, even some versions). At 28 I worked without it.
To establish a connection, participants need to exchange ICE candidates through a signaling server (they contain data about the network interface, address, etc.). When each candidate appears, the pc.onicecandidate event will be triggered (it will start to work after the local session is set using the setLocalDescription method, which will be discussed below).
We are preparing to accept candidates of another participant:
observer.on('ICE', function(ice) {
Next, Alice creates a channel. This channel will be used for data transmission:
function openOfferChannel() {
In the next step, Alice creates and sends “Offer” to Bob (session description with various service information,
SDP ).
function createOffer() { pc.createOffer(function(offer) { pc.setLocalDescription(offer, function() {
Now Alice is waiting for Bob’s session from the signaling server. When this happens, the setRemoteSDP function is called.
function setRemoteSDP(sdp) { pc.setRemoteDescription(new _RTCSessionDescription(sdp), function() { if(pc.remoteDescription.type == 'offer') {
Meanwhile, Bob receives Alice’s session from the signaling server, for his part, creates an RTCPeerConnection object and is preparing to accept the channel (this is called from the createPC function).
function openAnswerChannel() { pc.ondatachannel = function(e) { channel = e.channel; if(browser.mozilla) channel.binaryType = 'blob'; setChannelEvents(); }; }
Finally, Bob saves Alice’s session, creates his own and sends it to Alice.
function createAnswer() { pc.createAnswer(function(offer) { pc.setLocalDescription(offer, function() {
Message exchange
After the successful implementation of setRemoteDescription () for both participants and the exchange of ICE candidates, the connection between Alice and Bob should be established. In this case, the channel.onopen event fires in Chrome and Firefox, and in pc.onconnection also fires in Firefox.
Now Alice and Bob can exchange messages using the channel.send () method:
channel.send("Hi there!");
When a message arrives, the channel.onmessage event fires.
Disconnect definition
When another participant completes the connection, two events fire up in Firefox at once: pc.onclosedconnection and channel.onclose.
But in Chrome nothing works, but for the pc object, the value of the iceConnectionState property changes to “disconnected” (according to my observations, it does not change immediately, but after a few seconds). Therefore, you will have to make a small crutch until the developers have fixed the event call.
if(browser.chrome) { setInterval(function() { if(pc.iceConnectionState == "disconnected") { console.log("Disconnected"); } }, 1000); }
Current issues
- I want to pay attention that today Chrome can send data no more than ~ 1100 bytes in length. Therefore, in order to send something more, you will have to divide the message and send it in parts. Firefox can already send large messages, it has no such problems.
- Another serious drawback is that while Chrome and Firefox are incompatible with each other (setRemoteDescription () with another browser session throws an error, the connection will not be established).
- Theoretically, this way you can send both text and binary data. In Firefox, there are no problems with this, and the situation with Chrome is incomprehensible: they write on the Internet that binary data is not sent, and they wait for the developers to fix it, but I managed to send and receive them in Chrome 28 as well. Maybe I do not understand something.
Conclusion
The technology seems to me very promising, and now you can start experimenting with its implementation, albeit with significant limitations.
And here is a
link to a simple chat, the creation of which inspired me to this article. I wrote it exclusively for training and studying WebRTC, and in Chrome it will not be able to send more than ~ 1100 bytes (I did not do the breakdown).
Information sources: