![Sam Dutton](https://web.dev/images/authors/samdutton.jpg) Sam Dutton [X](https://twitter.com/sw12) [GitHub](https://github.com/samdutton) [Mastodon](https://techhub.social/@samdutton) [Bluesky](https://bsky.app/profile/samdutton.bsky.social) [Homepage](https://samdutton.com)

<br />


Published: November 04, 2013

<br />

[WebRTC](https://web.dev/articles/webrtc-basics) enables peer-to-peer communication. It
requires servers so that clients can exchange metadata and coordinate
communication through a process called *signaling*. Servers also help clients
cope with network address translators (NATs) and firewalls.

Here, you learn how to build a signaling service and deal with the quirks of
real-world connectivity with STUN and TURN servers. You'll also understand how
WebRTC apps can handle multi-party calls and interact with services, such as
VoIP and PSTN (also known as telephones).

## What is signaling?

Signaling is the process of coordinating communication. In order for a WebRTC
app to set up a call, its clients need to exchange the following information:

- Session-control messages used to open or close communication
- Error messages
- Media metadata, such as codecs, codec settings, bandwidth, and media types
- Key data used to establish secure connections
- Network data, such as a host's IP address and port as seen by the outside world

This signaling process needs a way for clients to pass messages back and forth.
That mechanism is not implemented by the WebRTC APIs. Instead, you need to build
it yourself.

### Why is signaling not defined by WebRTC?

To avoid redundancy and to maximize compatibility with established technologies,
signaling methods and protocols are not specified by WebRTC standards. This
approach is outlined by the
[JavaScript Session Establishment Protocol (JSEP)](https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-03#section-1.1):

> [!NOTE]
> **Note:** This WebRTC call setup fully specifies and controls the media plane, but leaves the signaling plane up to the app as much as possible. The rationale is that different apps may prefer to use different protocols, such as the existing SIP or Jingle call signaling protocols, or something custom for the particular app. In this approach, the key information to exchange is the multimedia session description, which specifies the necessary transport and media configuration information to establish the media plane.

JSEP's architecture also avoids a browser having to save state, that is, to
function as a signaling state machine. This would be problematic if, for
example, signaling data was lost each time a page was reloaded. Instead,
signaling state can be saved on a server.
![](https://web.dev/static/articles/webrtc-infrastructure/image/jsep-architecture-diagram.png) JSEP architecture diagram

JSEP requires the exchange between peers of *offer* and *answer*, the media
metadata. Offers and answers are communicated in Session Description Protocol
(SDP) format, which look like this:

    v=0
    o=- 7614219274584779017 2 IN IP4 127.0.0.1
    s=-
    t=0 0
    a=group:BUNDLE audio video
    a=msid-semantic: WMS
    m=audio 1 RTP/SAVPF 111 103 104 0 8 107 106 105 13 126
    c=IN IP4 0.0.0.0
    a=rtcp:1 IN IP4 0.0.0.0
    a=ice-ufrag:W2TGCZw2NZHuwlnf
    a=ice-pwd:xdQEccP40E+P0L5qTyzDgfmW
    a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
    a=mid:audio
    a=rtcp-mux
    a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:9c1AHz27dZ9xPI91YNfSlI67/EMkjHHIHORiClQe
    a=rtpmap:111 opus/48000/2
    ...

Want to know what all this SDP gobbledygook actually means? Take a look at the
[Internet Engineering Task Force (IETF) examples](https://datatracker.ietf.org/doc/draft-nandakumar-rtcweb-sdp/?include_text=1).

Bear in mind that WebRTC is designed so that the offer or answer can be tweaked
before being set as the local or remote description by editing the values in the
SDP text. For example, the `preferAudioCodec()` function in
[appr.tc](https://appr.tc) can be used to set the default codec and bitrate. SDP
is somewhat painful to manipulate with JavaScript and there is discussion about
whether future versions of WebRTC should use JSON instead, but there are
[some advantages](https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-03#section-3.3)
to sticking with SDP.

### `RTCPeerConnection` API and signaling: Offer, answer, and candidate

`RTCPeerConnection` is the API used by WebRTC apps to create a connection
between peers, and communicate audio and video.

To initialize this process, `RTCPeerConnection` has two tasks:

- Ascertain local media conditions, such as resolution and codec capabilities. This is the metadata used for the offer-and-answer mechanism.
- Get potential network addresses for the app's host, known as *candidates*.

Once this local data has been ascertained, it must be exchanged through a
signaling mechanism with the remote peer.

Imagine [Alice is trying to call Eve](https://xkcd.com/177/). Here's the full
answer mechanism in all its gory detail:

1. Alice creates an `RTCPeerConnection` object.
2. Alice creates an offer (an SDP session description) with the `RTCPeerConnection` `createOffer()` method.
3. Alice calls `setLocalDescription()` with her offer.
4. Alice stringifies the offer and uses a signaling mechanism to send it to Eve.
5. Eve calls `setRemoteDescription()` with Alice's offer, so that her `RTCPeerConnection` knows about Alice's setup.
6. Eve calls `createAnswer()` and the success callback for this is passed a local session description - Eve's answer.
7. Eve sets her answer as the local description by calling `setLocalDescription()`.
8. Eve then uses the signaling mechanism to send her stringified answer to Alice.
9. Alice sets Eve's answer as the remote session description using `setRemoteDescription()`.

Alice and Eve also need to exchange network information. The expression
"finding candidates" refers to the process of finding network interfaces and
ports using the [ICE framework](https://en.wikipedia.org/wiki/Interactive_Connectivity_Establishment).

1. Alice creates an `RTCPeerConnection` object with an `onicecandidate` handler.
2. The handler is called when network candidates become available.
3. In the handler, Alice sends stringified candidate data to Eve through their signaling channel.
4. When Eve gets a candidate message from Alice, she calls `addIceCandidate()` to add the candidate to the remote peer description.

JSEP supports [ICE Candidate Trickling](https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-03#section-3.4.1), which allows the caller to incrementally provide candidates to the callee after the initial offer, and for the callee to begin acting on the call and set up a connection without waiting for all candidates to arrive.

### Code WebRTC for signaling

The following code snippet is a [W3C code example](https://w3c.github.io/webrtc-pc/#simple-peer-to-peer-example) that summarizes the complete signaling process. The code assumes the existence of some signaling mechanism, `SignalingChannel`. Signaling is discussed in greater detail later.

    // handles JSON.stringify/parse
    const signaling = new SignalingChannel();
    const constraints = {audio: true, video: true};
    const configuration = {iceServers: [{urls: 'stun:stun.example.org'}]};
    const pc = new RTCPeerConnection(configuration);

    // Send any ice candidates to the other peer.
    pc.onicecandidate = ({candidate}) => signaling.send({candidate});

    // Let the "negotiationneeded" event trigger offer generation.
    pc.onnegotiationneeded = async () => {
      try {
        await pc.setLocalDescription(await pc.createOffer());
        // send the offer to the other peer
        signaling.send({desc: pc.localDescription});
      } catch (err) {
        console.error(err);
      }
    };

    // After remote track media arrives, show it in remote video element.
    pc.ontrack = (event) => {
      // Don't set srcObject again if it is already set.
      if (remoteView.srcObject) return;
      remoteView.srcObject = event.streams[0];
    };

    // Call start() to initiate.
    async function start() {
      try {
        // Get local stream, show it in self-view, and add it to be sent.
        const stream =
          await navigator.mediaDevices.getUserMedia(constraints);
        stream.getTracks().forEach((track) =>
          pc.addTrack(track, stream));
        selfView.srcObject = stream;
      } catch (err) {
        console.error(err);
      }
    }

    signaling.onmessage = async ({desc, candidate}) => {
      try {
        if (desc) {
          // If you get an offer, you need to reply with an answer.
          if (desc.type === 'offer') {
            await pc.setRemoteDescription(desc);
            const stream =
              await navigator.mediaDevices.getUserMedia(constraints);
            stream.getTracks().forEach((track) =>
              pc.addTrack(track, stream));
            await pc.setLocalDescription(await pc.createAnswer());
            signaling.send({desc: pc.localDescription});
          } else if (desc.type === 'answer') {
            await pc.setRemoteDescription(desc);
          } else {
            console.log('Unsupported SDP type.');
          }
        } else if (candidate) {
          await pc.addIceCandidate(candidate);
        }
      } catch (err) {
        console.error(err);
      }
    };

To see the offer-answer and candidate-exchange processes in action, visit
[simpl.info RTCPeerConnection](https://simpl.info/rtcpeerconnection/) and look
at the console log for a single-page video chat example. You can download a
complete dump of WebRTC signaling and stats from the `about://webrtc-internals`
page in Google Chrome or the `opera://webrtc-internals` page in Opera.

### Peer discovery

This is a fancy way of asking, "How do I find someone to talk to?"

For telephone calls, you have telephone numbers and directories. For online
video chat and messaging, you need identity and presence management systems, and
a means for users to initiate sessions. WebRTC apps need a way for clients to
signal to each other that they want to start or join a call.

Peer discovery mechanisms are not defined by WebRTC and you don't go into the
options here. The process can be as small as emailing or messaging a URL. For
video chat apps, such as [Talky](https://talky.io), [tawk.to](https://tawk.com)
and [Browser Meeting](https://browsermeeting.com), you invite people to a call
by sharing a custom link. Developer Chris Ball built an intriguing
[serverless-webrtc](https://blog.printf.net/articles/2013/05/17/webrtc-without-a-signaling-server/)
experiment that enables WebRTC call participants to exchange metadata by any
messaging service they like, such as IM, email, or homing pigeon.

## How can you build a signaling service?

To reiterate, signaling protocols and mechanisms are not defined by WebRTC
standards. Whatever you choose, you need an intermediary server to exchange
signaling messages and app data between clients. Sadly, a web app cannot shout
into the internet, "Connect me to my friend!"

Thankfully signaling messages are small and mostly exchanged at the start of a
call. In testing with [appr.tc](https://appr.tc) for a video chat session, a
total of around 30-45 messages were handled by the signaling service with a
total size for all messages of around 10KB.

As well as being relatively undemanding in terms of bandwidth, WebRTC signaling
services don't consume much processing or memory because they only need to relay
messages and retain a small amount of session state data, such as which clients
are connected.

> [!NOTE]
> **Note:** The signaling mechanism used to exchange session metadata can also be used to communicate app data.

### Push messages from server to client


Browser Support

- ![Chrome: 6.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='-10 -10 276 276'%3E%3ClinearGradient id='a' x1='145' x2='34' y1='253' y2='61' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%231e8e3e'/%3E%3Cstop offset='1' stop-color='%2334a853'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='111' x2='222' y1='254' y2='62' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fcc934'/%3E%3Cstop offset='1' stop-color='%23fbbc04'/%3E%3C/linearGradient%3E%3ClinearGradient id='c' x1='17' x2='239' y1='80' y2='80' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23d93025'/%3E%3Cstop offset='1' stop-color='%23ea4335'/%3E%3C/linearGradient%3E%3Ccircle cx='128' cy='128' r='64' fill='%23fff'/%3E%3Cpath fill='url(%23a)' d='M96 183a64 64 0 0 1-23-23L17 64a128 128 0 0 0 111 192l55-96a64 64 0 0 1-87 23Z'/%3E%3Cpath fill='url(%23b)' d='M192 128a64 64 0 0 1-9 32l-55 96A128 128 0 0 0 239 64H128a64 64 0 0 1 64 64Z'/%3E%3Ccircle cx='128' cy='128' r='52' fill='%231a73e8'/%3E%3Cpath fill='url(%23c)' d='M96 73a64 64 0 0 1 32-9h111a128 128 0 0 0-222 0l56 96a64 64 0 0 1 23-87Z'/%3E%3C/svg%3E)
- ![Edge: 79.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='0 0 27600 27600'%3E%3ClinearGradient id='A' gradientUnits='userSpaceOnUse'/%3E%3ClinearGradient id='B' x1='6870' x2='24704' y1='18705' y2='18705' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%230c59a4'/%3E%3Cstop offset='1' stop-color='%23114a8b'/%3E%3C/linearGradient%3E%3ClinearGradient id='C' x1='16272' x2='5133' y1='10968' y2='23102' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%231b9de2'/%3E%3Cstop offset='.16' stop-color='%231595df'/%3E%3Cstop offset='.67' stop-color='%230680d7'/%3E%3Cstop offset='1' stop-color='%230078d4'/%3E%3C/linearGradient%3E%3CradialGradient id='D' cx='16720' cy='18747' r='9538' xlink:href='%23A'%3E%3Cstop offset='.72' stop-opacity='0'/%3E%3Cstop offset='.95' stop-opacity='.53'/%3E%3Cstop offset='1'/%3E%3C/radialGradient%3E%3CradialGradient id='E' cx='7130' cy='19866' r='14324' gradientTransform='matrix(.14843 -.98892 .79688 .1196 -8759 25542)' xlink:href='%23A'%3E%3Cstop offset='.76' stop-opacity='0'/%3E%3Cstop offset='.95' stop-opacity='.5'/%3E%3Cstop offset='1'/%3E%3C/radialGradient%3E%3CradialGradient id='F' cx='2523' cy='4680' r='20243' gradientTransform='matrix(-.03715 .99931 -2.12836 -.07913 13579 3530)' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%2335c1f1'/%3E%3Cstop offset='.11' stop-color='%2334c1ed'/%3E%3Cstop offset='.23' stop-color='%232fc2df'/%3E%3Cstop offset='.31' stop-color='%232bc3d2'/%3E%3Cstop offset='.67' stop-color='%2336c752'/%3E%3C/radialGradient%3E%3CradialGradient id='G' cx='24247' cy='7758' r='9734' gradientTransform='matrix(.28109 .95968 -.78353 .22949 24510 -16292)' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%2366eb6e'/%3E%3Cstop offset='1' stop-color='%2366eb6e' stop-opacity='0'/%3E%3C/radialGradient%3E%3Cpath id='H' d='M24105 20053a9345 9345 0 01-1053 472 10202 10202 0 01-3590 646c-4732 0-8855-3255-8855-7432 0-1175 680-2193 1643-2729-4280 180-5380 4640-5380 7253 0 7387 6810 8137 8276 8137 791 0 1984-230 2704-456l130-44a12834 12834 0 006660-5282c220-350-168-757-535-565z'/%3E%3Cpath id='I' d='M11571 25141a7913 7913 0 01-2273-2137 8145 8145 0 01-1514-4740 8093 8093 0 013093-6395 8082 8082 0 011373-859c312-148 846-414 1554-404a3236 3236 0 012569 1297 3184 3184 0 01636 1866c0-21 2446-7960-8005-7960-4390 0-8004 4166-8004 7820 0 2319 538 4170 1212 5604a12833 12833 0 007684 6757 12795 12795 0 003908 610c1414 0 2774-233 4045-656a7575 7575 0 01-6278-803z'/%3E%3Cpath id='J' d='M16231 15886c-80 105-330 250-330 566 0 260 170 512 472 723 1438 1003 4149 868 4156 868a5954 5954 0 003027-839 6147 6147 0 001133-850 6180 6180 0 001910-4437c26-2242-796-3732-1133-4392-2120-4141-6694-6525-11668-6525-7011 0-12703 5635-12798 12620 47-3654 3679-6605 7996-6605 350 0 2346 34 4200 1007 1634 858 2490 1894 3086 2921 618 1067 728 2415 728 2952s-271 1333-780 1990z'/%3E%3Cuse fill='url(%23B)' xlink:href='%23H'/%3E%3Cuse fill='url(%23D)' opacity='.35' xlink:href='%23H'/%3E%3Cuse fill='url(%23C)' xlink:href='%23I'/%3E%3Cuse fill='url(%23E)' opacity='.4' xlink:href='%23I'/%3E%3Cuse fill='url(%23F)' xlink:href='%23J'/%3E%3Cuse fill='url(%23G)' xlink:href='%23J'/%3E%3C/svg%3E)
- ![Firefox: 6.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 512 512'%3E%3Cdefs%3E%3CradialGradient id='ff-b' cx='428.5' cy='55.1' r='501' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23ffbd4f'/%3E%3Cstop offset='.2' stop-color='%23ffac31'/%3E%3Cstop offset='.3' stop-color='%23ff9d17'/%3E%3Cstop offset='.3' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff563b'/%3E%3Cstop offset='.5' stop-color='%23ff3750'/%3E%3Cstop offset='.7' stop-color='%23f5156c'/%3E%3Cstop offset='.8' stop-color='%23eb0878'/%3E%3Cstop offset='.9' stop-color='%23e50080'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-c' cx='245.4' cy='259.9' r='501' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.3' stop-color='%23960e18'/%3E%3Cstop offset='.3' stop-color='%23b11927' stop-opacity='.7'/%3E%3Cstop offset='.4' stop-color='%23db293d' stop-opacity='.3'/%3E%3Cstop offset='.5' stop-color='%23f5334b' stop-opacity='.1'/%3E%3Cstop offset='.5' stop-color='%23ff3750' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-d' cx='305.8' cy='-58.6' r='363' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.3' stop-color='%23ffdc3e'/%3E%3Cstop offset='.5' stop-color='%23ff9d12'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-e' cx='190' cy='390.8' r='238.6' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.3' stop-color='%233a8ee6'/%3E%3Cstop offset='.5' stop-color='%235c79f0'/%3E%3Cstop offset='.7' stop-color='%239059ff'/%3E%3Cstop offset='1' stop-color='%23c139e6'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-f' cx='252.2' cy='201.3' r='126.5' gradientTransform='matrix(1 0 0 1 -48 31)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.2' stop-color='%239059ff' stop-opacity='0'/%3E%3Cstop offset='.3' stop-color='%238c4ff3' stop-opacity='.1'/%3E%3Cstop offset='.8' stop-color='%237716a8' stop-opacity='.5'/%3E%3Cstop offset='1' stop-color='%236e008b' stop-opacity='.6'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-g' cx='239.1' cy='34.6' r='171.6' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23ffe226'/%3E%3Cstop offset='.1' stop-color='%23ffdb27'/%3E%3Cstop offset='.3' stop-color='%23ffc82a'/%3E%3Cstop offset='.5' stop-color='%23ffa930'/%3E%3Cstop offset='.7' stop-color='%23ff7e37'/%3E%3Cstop offset='.8' stop-color='%23ff7139'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-h' cx='374' cy='-74.3' r='732.2' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3Cstop offset='.6' stop-color='%23ff5634'/%3E%3Cstop offset='.7' stop-color='%23ff3647'/%3E%3Cstop offset='.9' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-i' cx='304.6' cy='7.1' r='536.4' gradientTransform='rotate(84 303 4)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fff44f'/%3E%3Cstop offset='.1' stop-color='%23ffe847'/%3E%3Cstop offset='.2' stop-color='%23ffc830'/%3E%3Cstop offset='.3' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff8b16'/%3E%3Cstop offset='.5' stop-color='%23ff672a'/%3E%3Cstop offset='.6' stop-color='%23ff3647'/%3E%3Cstop offset='.7' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-j' cx='235' cy='98.1' r='457.1' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3Cstop offset='.6' stop-color='%23ff5634'/%3E%3Cstop offset='.7' stop-color='%23ff3647'/%3E%3Cstop offset='.9' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-k' cx='355.7' cy='124.9' r='500.3' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.2' stop-color='%23ffe141'/%3E%3Cstop offset='.5' stop-color='%23ffaf1e'/%3E%3Cstop offset='.6' stop-color='%23ff980e'/%3E%3C/radialGradient%3E%3ClinearGradient id='ff-a' x1='446.9' y1='76.8' x2='47.9' y2='461.8' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.1' stop-color='%23ffe847'/%3E%3Cstop offset='.2' stop-color='%23ffc830'/%3E%3Cstop offset='.4' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff8b16'/%3E%3Cstop offset='.5' stop-color='%23ff672a'/%3E%3Cstop offset='.5' stop-color='%23ff3647'/%3E%3Cstop offset='.7' stop-color='%23e31587'/%3E%3C/linearGradient%3E%3ClinearGradient id='ff-l' x1='442.1' y1='74.8' x2='102.6' y2='414.3' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.2' stop-color='%23fff44f' stop-opacity='.8'/%3E%3Cstop offset='.3' stop-color='%23fff44f' stop-opacity='.6'/%3E%3Cstop offset='.5' stop-color='%23fff44f' stop-opacity='.2'/%3E%3Cstop offset='.6' stop-color='%23fff44f' stop-opacity='0'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134a120 120 0 0 0-66 25 71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a229 229 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zM202 355l3 1-3-1zm55-145zm198-31z' fill='url(%23ff-a)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z' fill='url(%23ff-b)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z' fill='url(%23ff-c)'/%3E%3Cpath d='m362 195 1 1a130 130 0 0 0-22-29C266 92 322 5 331 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62z' fill='url(%23ff-d)'/%3E%3Cpath d='M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z' fill='url(%23ff-e)'/%3E%3Cpath d='M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z' fill='url(%23ff-f)'/%3E%3Cpath d='m171 151 5 3a111 111 0 0 1-1-58c-25 11-44 29-58 44 1 0 36 0 54 11z' fill='url(%23ff-g)'/%3E%3Cpath d='M18 261a242 242 0 0 0 231 197 207 207 0 0 0 206-279c8 56-20 110-64 146-86 71-169 43-186 31l-3-1c-50-24-71-70-67-110-42 0-57-35-57-35s38-28 89-4c46 22 90 4 90 4 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5l-5-3c-18-11-52-11-54-11-9-12-9-51-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73c0 1-9 38-5 57z' fill='url(%23ff-h)'/%3E%3Cpath d='M341 167a130 130 0 0 1 22 29 46 46 0 0 1 4 3c55 50 26 121 24 126 44-36 72-90 64-146-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-9 5-65 92 10 167z' fill='url(%23ff-i)'/%3E%3Cpath d='M367 199a46 46 0 0 0-4-3l-1-1c-13-9-36-18-58-15 86 44 63 193-57 187a107 107 0 0 1-31-6 131 131 0 0 1-11-5c17 12 99 39 186-31 2-5 31-76-24-126z' fill='url(%23ff-j)'/%3E%3Cpath d='M148 277s12-41 80-41c7 0 28-20 29-26s-44 18-90-4c-51-24-89 4-89 4s15 35 57 35c-4 40 16 85 67 110l3 1c-29-15-54-44-57-79z' fill='url(%23ff-k)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62-13-9-36-18-58-14 86 43 63 192-57 186a107 107 0 0 1-31-6 131 131 0 0 1-11-5l-3-1 3 1c-29-15-54-44-57-79 0 0 12-41 80-41 7 0 28-20 29-26 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a279 279 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zm-24 13z' fill='url(%23ff-l)'/%3E%3C/svg%3E)
- ![Safari: 5.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='195 190 135 135'%3E%3Cdefs%3E%3ClinearGradient id='s-a' x1='132.6' x2='134.4' y1='111.7' y2='-105.3' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23d2d2d2' /%3E%3Cstop offset='.5' stop-color='%23f2f2f2' /%3E%3Cstop offset='1' stop-color='%23fff' /%3E%3C/linearGradient%3E%3ClinearGradient id='s-b' gradientUnits='userSpaceOnUse' /%3E%3ClinearGradient id='s-c' x1='65.4' x2='67.4' y1='115.7' y2='17.1' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23005ad5' /%3E%3Cstop offset='.2' stop-color='%230875f0' /%3E%3Cstop offset='.3' stop-color='%23218cee' /%3E%3Cstop offset='.6' stop-color='%2327a5f3' /%3E%3Cstop offset='.8' stop-color='%2325aaf2' /%3E%3Cstop offset='1' stop-color='%2321aaef' /%3E%3C/linearGradient%3E%3ClinearGradient id='s-d' x1='158.7' x2='176.3' y1='96.7' y2='79.5' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23c72e24' /%3E%3Cstop offset='1' stop-color='%23fd3b2f' /%3E%3C/linearGradient%3E%3CradialGradient id='s-i' cx='-69.9' cy='69.3' r='54' gradientTransform='matrix(.9 -.01 .04 2.72 -9 -120)' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%2324a5f3' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%231e8ceb' /%3E%3C/radialGradient%3E%3CradialGradient id='s-j' cx='109.3' cy='13.8' r='93.1' gradientTransform='matrix(-.02 1.1 -1.04 -.02 137 -115)' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%235488d6' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%235d96eb' /%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width='220' height='220' x='22' y='-107' fill='url(%23s-a)' ry='49' transform='matrix(.57 0 0 .57 187 256)' /%3E%3Cg transform='translate(194 190)'%3E%3Ccircle cx='67.8' cy='67.7' fill='url(%23s-c)' paint-order='stroke fill markers' r='54' /%3E%3Ccircle cx='-69.9' cy='69.3' fill='url(%23s-i)' transform='translate(138 -2)' r='54' /%3E%3C/g%3E%3Cellipse cx='120' cy='14.2' fill='url(%23s-j)' rx='93.1' ry='93.7' transform='matrix(.58 0 0 .58 192 250)' /%3E%3Cg transform='matrix(.58 0 0 .57 197 182)'%3E%3Cpath fill='%23cac7c8' d='M46 192h1l72-48-7-9-66 57Z' /%3E%3Cpath fill='%23fbfffc' d='M46 191v1l66-57-7-9-59 65Z' /%3E%3Cpath fill='url(%23s-d)' d='m119 144-7-9 66-57-59 66Z' /%3E%3Cpath fill='%23fb645c' d='m105 126 7 9 66-57-1-1-72 49Z' /%3E%3C/g%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-miterlimit='1' stroke-width='1.3' d='m287 278 3-2m-12-17 8-2m-8-3h4m-4-13 8 2m-8 3h4m-1-13 7 3m-4-11 7 4m-2-11 6 6m0-12 6 7m1-11 4 6m4-10 3 7m5-9 2 7m15-7-1 7m10-5-3 7m11-4-4 7m11-2-5 6m16 7-7 4m10 4-7 3m10 6-8 1m8 16-8-2m5 10-7-3m4 11-7-4m2 11-6-5m0 11-5-6m-2 11-4-7m-4 11-3-8m-6 10-1-8m-16 8 2-8m-10 5 3-7m-11 4 4-7m-11 2 5-6m-8 3 3-3m4 8 2-3m5 8 2-4m6 7 1-4m8 5v-4m8 4v-4m9 3-1-4m9 1-2-4m9 0-2-4m9-2-3-3m8-4-3-2m8-5-4-2m7-6-4-1m5-8h-4m4-8h-4m3-9-4 1m1-9-4 2m-1-9-3 2m-2-9-3 3m-4-8-2 3m-5-8-2 4m-6-6-1 3m-8-5v4m-8-4v4m-9-2 1 3m-9 0 2 3m-9 1 2 3m-9 2 3 3m-8 4 3 2m-8 5 4 2m-7 6 4 1m-4 25 4-1m-2 5 7-3m-6 7 4-2m-2 6 7-4m-13-21h8m41-41v-8m0 99v-8m49-42h-8' transform='translate(-65 8)' /%3E%3C/svg%3E)

[Source](https://developer.mozilla.org/docs/Web/API/EventSource)

<br />

A message service for signaling needs to be bidirectional: client-to-server and
server-to-client. Bidirectional communication goes against the HTTP client/server
request/response model, but various hacks such as
[long polling](https://wikipedia.org/wiki/Comet_(programming)) have been
developed over many years in order to push data from a service running on a web
server to a web app running in a browser.

The [`EventSource` API](https://developer.mozilla.org/docs/Web/API/EventSource)
enables server-sent events, data sent from a web server to a browser client
through HTTP. `EventSource` is designed for one-way messaging, but it can be
used in combination with XHR to build a service for exchanging signaling
messages. A signaling service passes a message from a caller, delivered by XHR
request, by pushing it through `EventSource` to the callee.


Browser Support

- ![Chrome: 5.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='-10 -10 276 276'%3E%3ClinearGradient id='a' x1='145' x2='34' y1='253' y2='61' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%231e8e3e'/%3E%3Cstop offset='1' stop-color='%2334a853'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='111' x2='222' y1='254' y2='62' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fcc934'/%3E%3Cstop offset='1' stop-color='%23fbbc04'/%3E%3C/linearGradient%3E%3ClinearGradient id='c' x1='17' x2='239' y1='80' y2='80' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23d93025'/%3E%3Cstop offset='1' stop-color='%23ea4335'/%3E%3C/linearGradient%3E%3Ccircle cx='128' cy='128' r='64' fill='%23fff'/%3E%3Cpath fill='url(%23a)' d='M96 183a64 64 0 0 1-23-23L17 64a128 128 0 0 0 111 192l55-96a64 64 0 0 1-87 23Z'/%3E%3Cpath fill='url(%23b)' d='M192 128a64 64 0 0 1-9 32l-55 96A128 128 0 0 0 239 64H128a64 64 0 0 1 64 64Z'/%3E%3Ccircle cx='128' cy='128' r='52' fill='%231a73e8'/%3E%3Cpath fill='url(%23c)' d='M96 73a64 64 0 0 1 32-9h111a128 128 0 0 0-222 0l56 96a64 64 0 0 1 23-87Z'/%3E%3C/svg%3E)
- ![Edge: 12.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='0 0 27600 27600'%3E%3ClinearGradient id='A' gradientUnits='userSpaceOnUse'/%3E%3ClinearGradient id='B' x1='6870' x2='24704' y1='18705' y2='18705' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%230c59a4'/%3E%3Cstop offset='1' stop-color='%23114a8b'/%3E%3C/linearGradient%3E%3ClinearGradient id='C' x1='16272' x2='5133' y1='10968' y2='23102' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%231b9de2'/%3E%3Cstop offset='.16' stop-color='%231595df'/%3E%3Cstop offset='.67' stop-color='%230680d7'/%3E%3Cstop offset='1' stop-color='%230078d4'/%3E%3C/linearGradient%3E%3CradialGradient id='D' cx='16720' cy='18747' r='9538' xlink:href='%23A'%3E%3Cstop offset='.72' stop-opacity='0'/%3E%3Cstop offset='.95' stop-opacity='.53'/%3E%3Cstop offset='1'/%3E%3C/radialGradient%3E%3CradialGradient id='E' cx='7130' cy='19866' r='14324' gradientTransform='matrix(.14843 -.98892 .79688 .1196 -8759 25542)' xlink:href='%23A'%3E%3Cstop offset='.76' stop-opacity='0'/%3E%3Cstop offset='.95' stop-opacity='.5'/%3E%3Cstop offset='1'/%3E%3C/radialGradient%3E%3CradialGradient id='F' cx='2523' cy='4680' r='20243' gradientTransform='matrix(-.03715 .99931 -2.12836 -.07913 13579 3530)' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%2335c1f1'/%3E%3Cstop offset='.11' stop-color='%2334c1ed'/%3E%3Cstop offset='.23' stop-color='%232fc2df'/%3E%3Cstop offset='.31' stop-color='%232bc3d2'/%3E%3Cstop offset='.67' stop-color='%2336c752'/%3E%3C/radialGradient%3E%3CradialGradient id='G' cx='24247' cy='7758' r='9734' gradientTransform='matrix(.28109 .95968 -.78353 .22949 24510 -16292)' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%2366eb6e'/%3E%3Cstop offset='1' stop-color='%2366eb6e' stop-opacity='0'/%3E%3C/radialGradient%3E%3Cpath id='H' d='M24105 20053a9345 9345 0 01-1053 472 10202 10202 0 01-3590 646c-4732 0-8855-3255-8855-7432 0-1175 680-2193 1643-2729-4280 180-5380 4640-5380 7253 0 7387 6810 8137 8276 8137 791 0 1984-230 2704-456l130-44a12834 12834 0 006660-5282c220-350-168-757-535-565z'/%3E%3Cpath id='I' d='M11571 25141a7913 7913 0 01-2273-2137 8145 8145 0 01-1514-4740 8093 8093 0 013093-6395 8082 8082 0 011373-859c312-148 846-414 1554-404a3236 3236 0 012569 1297 3184 3184 0 01636 1866c0-21 2446-7960-8005-7960-4390 0-8004 4166-8004 7820 0 2319 538 4170 1212 5604a12833 12833 0 007684 6757 12795 12795 0 003908 610c1414 0 2774-233 4045-656a7575 7575 0 01-6278-803z'/%3E%3Cpath id='J' d='M16231 15886c-80 105-330 250-330 566 0 260 170 512 472 723 1438 1003 4149 868 4156 868a5954 5954 0 003027-839 6147 6147 0 001133-850 6180 6180 0 001910-4437c26-2242-796-3732-1133-4392-2120-4141-6694-6525-11668-6525-7011 0-12703 5635-12798 12620 47-3654 3679-6605 7996-6605 350 0 2346 34 4200 1007 1634 858 2490 1894 3086 2921 618 1067 728 2415 728 2952s-271 1333-780 1990z'/%3E%3Cuse fill='url(%23B)' xlink:href='%23H'/%3E%3Cuse fill='url(%23D)' opacity='.35' xlink:href='%23H'/%3E%3Cuse fill='url(%23C)' xlink:href='%23I'/%3E%3Cuse fill='url(%23E)' opacity='.4' xlink:href='%23I'/%3E%3Cuse fill='url(%23F)' xlink:href='%23J'/%3E%3Cuse fill='url(%23G)' xlink:href='%23J'/%3E%3C/svg%3E)
- ![Firefox: 11.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 512 512'%3E%3Cdefs%3E%3CradialGradient id='ff-b' cx='428.5' cy='55.1' r='501' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23ffbd4f'/%3E%3Cstop offset='.2' stop-color='%23ffac31'/%3E%3Cstop offset='.3' stop-color='%23ff9d17'/%3E%3Cstop offset='.3' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff563b'/%3E%3Cstop offset='.5' stop-color='%23ff3750'/%3E%3Cstop offset='.7' stop-color='%23f5156c'/%3E%3Cstop offset='.8' stop-color='%23eb0878'/%3E%3Cstop offset='.9' stop-color='%23e50080'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-c' cx='245.4' cy='259.9' r='501' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.3' stop-color='%23960e18'/%3E%3Cstop offset='.3' stop-color='%23b11927' stop-opacity='.7'/%3E%3Cstop offset='.4' stop-color='%23db293d' stop-opacity='.3'/%3E%3Cstop offset='.5' stop-color='%23f5334b' stop-opacity='.1'/%3E%3Cstop offset='.5' stop-color='%23ff3750' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-d' cx='305.8' cy='-58.6' r='363' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.3' stop-color='%23ffdc3e'/%3E%3Cstop offset='.5' stop-color='%23ff9d12'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-e' cx='190' cy='390.8' r='238.6' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.3' stop-color='%233a8ee6'/%3E%3Cstop offset='.5' stop-color='%235c79f0'/%3E%3Cstop offset='.7' stop-color='%239059ff'/%3E%3Cstop offset='1' stop-color='%23c139e6'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-f' cx='252.2' cy='201.3' r='126.5' gradientTransform='matrix(1 0 0 1 -48 31)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.2' stop-color='%239059ff' stop-opacity='0'/%3E%3Cstop offset='.3' stop-color='%238c4ff3' stop-opacity='.1'/%3E%3Cstop offset='.8' stop-color='%237716a8' stop-opacity='.5'/%3E%3Cstop offset='1' stop-color='%236e008b' stop-opacity='.6'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-g' cx='239.1' cy='34.6' r='171.6' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23ffe226'/%3E%3Cstop offset='.1' stop-color='%23ffdb27'/%3E%3Cstop offset='.3' stop-color='%23ffc82a'/%3E%3Cstop offset='.5' stop-color='%23ffa930'/%3E%3Cstop offset='.7' stop-color='%23ff7e37'/%3E%3Cstop offset='.8' stop-color='%23ff7139'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-h' cx='374' cy='-74.3' r='732.2' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3Cstop offset='.6' stop-color='%23ff5634'/%3E%3Cstop offset='.7' stop-color='%23ff3647'/%3E%3Cstop offset='.9' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-i' cx='304.6' cy='7.1' r='536.4' gradientTransform='rotate(84 303 4)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fff44f'/%3E%3Cstop offset='.1' stop-color='%23ffe847'/%3E%3Cstop offset='.2' stop-color='%23ffc830'/%3E%3Cstop offset='.3' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff8b16'/%3E%3Cstop offset='.5' stop-color='%23ff672a'/%3E%3Cstop offset='.6' stop-color='%23ff3647'/%3E%3Cstop offset='.7' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-j' cx='235' cy='98.1' r='457.1' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3Cstop offset='.6' stop-color='%23ff5634'/%3E%3Cstop offset='.7' stop-color='%23ff3647'/%3E%3Cstop offset='.9' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-k' cx='355.7' cy='124.9' r='500.3' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.2' stop-color='%23ffe141'/%3E%3Cstop offset='.5' stop-color='%23ffaf1e'/%3E%3Cstop offset='.6' stop-color='%23ff980e'/%3E%3C/radialGradient%3E%3ClinearGradient id='ff-a' x1='446.9' y1='76.8' x2='47.9' y2='461.8' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.1' stop-color='%23ffe847'/%3E%3Cstop offset='.2' stop-color='%23ffc830'/%3E%3Cstop offset='.4' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff8b16'/%3E%3Cstop offset='.5' stop-color='%23ff672a'/%3E%3Cstop offset='.5' stop-color='%23ff3647'/%3E%3Cstop offset='.7' stop-color='%23e31587'/%3E%3C/linearGradient%3E%3ClinearGradient id='ff-l' x1='442.1' y1='74.8' x2='102.6' y2='414.3' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.2' stop-color='%23fff44f' stop-opacity='.8'/%3E%3Cstop offset='.3' stop-color='%23fff44f' stop-opacity='.6'/%3E%3Cstop offset='.5' stop-color='%23fff44f' stop-opacity='.2'/%3E%3Cstop offset='.6' stop-color='%23fff44f' stop-opacity='0'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134a120 120 0 0 0-66 25 71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a229 229 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zM202 355l3 1-3-1zm55-145zm198-31z' fill='url(%23ff-a)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z' fill='url(%23ff-b)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z' fill='url(%23ff-c)'/%3E%3Cpath d='m362 195 1 1a130 130 0 0 0-22-29C266 92 322 5 331 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62z' fill='url(%23ff-d)'/%3E%3Cpath d='M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z' fill='url(%23ff-e)'/%3E%3Cpath d='M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z' fill='url(%23ff-f)'/%3E%3Cpath d='m171 151 5 3a111 111 0 0 1-1-58c-25 11-44 29-58 44 1 0 36 0 54 11z' fill='url(%23ff-g)'/%3E%3Cpath d='M18 261a242 242 0 0 0 231 197 207 207 0 0 0 206-279c8 56-20 110-64 146-86 71-169 43-186 31l-3-1c-50-24-71-70-67-110-42 0-57-35-57-35s38-28 89-4c46 22 90 4 90 4 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5l-5-3c-18-11-52-11-54-11-9-12-9-51-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73c0 1-9 38-5 57z' fill='url(%23ff-h)'/%3E%3Cpath d='M341 167a130 130 0 0 1 22 29 46 46 0 0 1 4 3c55 50 26 121 24 126 44-36 72-90 64-146-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-9 5-65 92 10 167z' fill='url(%23ff-i)'/%3E%3Cpath d='M367 199a46 46 0 0 0-4-3l-1-1c-13-9-36-18-58-15 86 44 63 193-57 187a107 107 0 0 1-31-6 131 131 0 0 1-11-5c17 12 99 39 186-31 2-5 31-76-24-126z' fill='url(%23ff-j)'/%3E%3Cpath d='M148 277s12-41 80-41c7 0 28-20 29-26s-44 18-90-4c-51-24-89 4-89 4s15 35 57 35c-4 40 16 85 67 110l3 1c-29-15-54-44-57-79z' fill='url(%23ff-k)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62-13-9-36-18-58-14 86 43 63 192-57 186a107 107 0 0 1-31-6 131 131 0 0 1-11-5l-3-1 3 1c-29-15-54-44-57-79 0 0 12-41 80-41 7 0 28-20 29-26 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a279 279 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zm-24 13z' fill='url(%23ff-l)'/%3E%3C/svg%3E)
- ![Safari: 5.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='195 190 135 135'%3E%3Cdefs%3E%3ClinearGradient id='s-a' x1='132.6' x2='134.4' y1='111.7' y2='-105.3' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23d2d2d2' /%3E%3Cstop offset='.5' stop-color='%23f2f2f2' /%3E%3Cstop offset='1' stop-color='%23fff' /%3E%3C/linearGradient%3E%3ClinearGradient id='s-b' gradientUnits='userSpaceOnUse' /%3E%3ClinearGradient id='s-c' x1='65.4' x2='67.4' y1='115.7' y2='17.1' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23005ad5' /%3E%3Cstop offset='.2' stop-color='%230875f0' /%3E%3Cstop offset='.3' stop-color='%23218cee' /%3E%3Cstop offset='.6' stop-color='%2327a5f3' /%3E%3Cstop offset='.8' stop-color='%2325aaf2' /%3E%3Cstop offset='1' stop-color='%2321aaef' /%3E%3C/linearGradient%3E%3ClinearGradient id='s-d' x1='158.7' x2='176.3' y1='96.7' y2='79.5' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23c72e24' /%3E%3Cstop offset='1' stop-color='%23fd3b2f' /%3E%3C/linearGradient%3E%3CradialGradient id='s-i' cx='-69.9' cy='69.3' r='54' gradientTransform='matrix(.9 -.01 .04 2.72 -9 -120)' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%2324a5f3' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%231e8ceb' /%3E%3C/radialGradient%3E%3CradialGradient id='s-j' cx='109.3' cy='13.8' r='93.1' gradientTransform='matrix(-.02 1.1 -1.04 -.02 137 -115)' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%235488d6' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%235d96eb' /%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width='220' height='220' x='22' y='-107' fill='url(%23s-a)' ry='49' transform='matrix(.57 0 0 .57 187 256)' /%3E%3Cg transform='translate(194 190)'%3E%3Ccircle cx='67.8' cy='67.7' fill='url(%23s-c)' paint-order='stroke fill markers' r='54' /%3E%3Ccircle cx='-69.9' cy='69.3' fill='url(%23s-i)' transform='translate(138 -2)' r='54' /%3E%3C/g%3E%3Cellipse cx='120' cy='14.2' fill='url(%23s-j)' rx='93.1' ry='93.7' transform='matrix(.58 0 0 .58 192 250)' /%3E%3Cg transform='matrix(.58 0 0 .57 197 182)'%3E%3Cpath fill='%23cac7c8' d='M46 192h1l72-48-7-9-66 57Z' /%3E%3Cpath fill='%23fbfffc' d='M46 191v1l66-57-7-9-59 65Z' /%3E%3Cpath fill='url(%23s-d)' d='m119 144-7-9 66-57-59 66Z' /%3E%3Cpath fill='%23fb645c' d='m105 126 7 9 66-57-1-1-72 49Z' /%3E%3C/g%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-miterlimit='1' stroke-width='1.3' d='m287 278 3-2m-12-17 8-2m-8-3h4m-4-13 8 2m-8 3h4m-1-13 7 3m-4-11 7 4m-2-11 6 6m0-12 6 7m1-11 4 6m4-10 3 7m5-9 2 7m15-7-1 7m10-5-3 7m11-4-4 7m11-2-5 6m16 7-7 4m10 4-7 3m10 6-8 1m8 16-8-2m5 10-7-3m4 11-7-4m2 11-6-5m0 11-5-6m-2 11-4-7m-4 11-3-8m-6 10-1-8m-16 8 2-8m-10 5 3-7m-11 4 4-7m-11 2 5-6m-8 3 3-3m4 8 2-3m5 8 2-4m6 7 1-4m8 5v-4m8 4v-4m9 3-1-4m9 1-2-4m9 0-2-4m9-2-3-3m8-4-3-2m8-5-4-2m7-6-4-1m5-8h-4m4-8h-4m3-9-4 1m1-9-4 2m-1-9-3 2m-2-9-3 3m-4-8-2 3m-5-8-2 4m-6-6-1 3m-8-5v4m-8-4v4m-9-2 1 3m-9 0 2 3m-9 1 2 3m-9 2 3 3m-8 4 3 2m-8 5 4 2m-7 6 4 1m-4 25 4-1m-2 5 7-3m-6 7 4-2m-2 6 7-4m-13-21h8m41-41v-8m0 99v-8m49-42h-8' transform='translate(-65 8)' /%3E%3C/svg%3E)

[Source](https://developer.mozilla.org/docs/Web/API/WebSocket)

<br />

[WebSocket](https://web.dev/articles/websockets-basics) is designed for full duplex
client--server communication, which means messages can flow in both directions at
the same time. One advantage of a signaling service built with pure WebSocket or
server-sent events (`EventSource`) is that the backend for these APIs can be
implemented on a variety of web frameworks common to most web-hosting packages
for languages such as PHP, Python, and Ruby.

All browsers that support WebRTC also support WebSocket.
[TLS](https://wikipedia.org/wiki/Transport_Layer_Security) should be used for
all connections to ensure messages cannot be intercepted unencrypted and also to
[reduce problems with proxy traversal](https://www.infoq.com/articles/Web-Sockets-Proxy-Servers).
For more information about WebSocket and proxy traversal, read the
[WebRTC chapter](https://hpbn.co/webrtc) in Ilya Grigorik's *High Performance
Browser Networking*.

You can handle signaling by getting WebRTC clients to poll a messaging server
repeatedly through Ajax, but that leads to a lot of redundant network requests,
which is especially problematic for mobile devices. Even after a session has
been established, peers need to poll for signaling messages in case of changes
or session termination by other peers. The [WebRTC Book](https://webrtcbook.com)
app example takes this option with some optimizations for polling frequency.

### Scale signaling

Although a signaling service consumes relatively little bandwidth and CPU per
client, signaling servers for a popular app may have to handle a lot of messages
from different locations with high levels of concurrency. WebRTC apps that get a
lot of traffic need signaling servers able to handle considerable load.
You don't go into detail here, but there are a number of options for
high-volume, high-performance messaging, including the following:

- [eXtensible Messaging and Presence Protocol](https://wikipedia.org/wiki/Xmpp)
  (XMPP) is a protocol developed for instant messaging that can be used for
  signaling. Server implementations include [ejabberd](https://en.wikipedia.org/wiki/Ejabberd)
  and [Openfire](https://en.wikipedia.org/wiki/Openfire). JavaScript clients,
  such as [Strophe.js](https://strophe.im/strophejs/), use
  [BOSH](https://en.wikipedia.org/wiki/BOSH) to emulate bidirectional streaming,
  but BOSH may be inefficient and scale poorly, compared to WebSocket.

- Open source libraries, such as [ZeroMQ](https://zeromq.org/) (as used by
  TokBox for their [Rumour](https://www.tokbox.com/blog/tokbox-builds-it%E2%80%99s-own-internal-messaging-infrastructure/) service) and
  [OpenMQ](https://en.wikipedia.org/wiki/Open_Message_Queue)
  ([NullMQ](https://avalanche123.com/blog/2012/02/25/interacting-with-zeromq-from-the-browser/)
  applies ZeroMQ concepts to web platforms using the
  [STOMP protocol](https://stomp.github.io/) over WebSocket.)

- Commercial cloud-messaging platforms that use WebSocket (though they may fall
  back to long polling), such as [Pusher](https://pusher.com/) and
  [Kaazing](https://kaazing.com/).

- Commercial WebRTC platforms, such as [vLine](https://vline.com/)

### Build a signaling service with Socket.io on Node

The following is code for a web app that uses a signaling service built with
[Socket.io](https://socket.io) on [Node](https://nodejs.org/). The design of
Socket.io makes it easier to build a service to exchange messages and Socket.io
is particularly suited to WebRTC signaling because of its built-in concept of
rooms. This example is designed as for a relatively small number of users,
rather than production use.

Socket.io uses WebSocket with fallbacks: AJAX long polling, AJAX multipart
streaming, Forever Iframe, and JSONP polling. It has been ported to various
backends, but is perhaps best known for its Node version used in this example.

There's no WebRTC in this example. It's designed to show how to build signaling
into a web app. View the console log to see what's happening as clients join a
room and exchange messages. This
[WebRTC codelab](https://codelabs.developers.google.com/codelabs/webrtc-web/#0)
gives step-by-step instructions for how to integrate this into a complete
WebRTC video chat app.

Here is the client `index.html`:

    <!DOCTYPE html>
    <html>
      <head>
        <title>WebRTC client</title>
      </head>
      <body>
        <script src='/socket.io/socket.io.js'></script>
        <script src='js/main.js'></script>
      </body>
    </html>

Here's the JavaScript file `main.js` referenced in the client:

    const isInitiator;

    room = prompt('Enter room name:');

    const socket = io.connect();

    if (room !== '') {
      console.log('Joining room ' + room);
      socket.emit('create or join', room);
    }

    socket.on('full', (room) => {
      console.log('Room ' + room + ' is full');
    });

    socket.on('empty', (room) => {
      isInitiator = true;
      console.log('Room ' + room + ' is empty');
    });

    socket.on('join', (room) => {
      console.log('Making request to join room ' + room);
      console.log('You are the initiator!');
    });

    socket.on('log', (array) => {
      console.log.apply(console, array);
    });

Here's the complete server app:

    const static = require('node-static');
    const http = require('http');
    const file = new(static.Server)();
    const app = http.createServer(function (req, res) {
      file.serve(req, res);
    }).listen(2013);

    const io = require('socket.io').listen(app);

    io.sockets.on('connection', (socket) => {

      // Convenience function to log server messages to the client
      function log(){
        const array = ['>>> Message from server: '];
        for (const i = 0; i < arguments.length; i++) {
          array.push(arguments[i]);
        }
          socket.emit('log', array);
      }

      socket.on('message', (message) => {
        log('Got message:', message);
        // For a real app, would be room only (not broadcast)
        socket.broadcast.emit('message', message);
      });

      socket.on('create or join', (room) => {
        const numClients = io.sockets.clients(room).length;

        log('Room ' + room + ' has ' + numClients + ' client(s)');
        log('Request to create or join room ' + room);

        if (numClients === 0){
          socket.join(room);
          socket.emit('created', room);
        } else if (numClients === 1) {
          io.sockets.in(room).emit('join', room);
          socket.join(room);
          socket.emit('joined', room);
        } else { // max two clients
          socket.emit('full', room);
        }
        socket.emit('emit(): client ' + socket.id +
          ' joined room ' + room);
        socket.broadcast.emit('broadcast(): client ' + socket.id +
          ' joined room ' + room);

      });

    });

To run this app on localhost, you need to have [Node.js](https://nodejs.org/),
Socket.IO, and [node-static](https://github.com/cloudhead/node-static). To
install Socket.IO and node-static, run `npm` from a terminal
in your app directory:

    npm install socket.io
    npm install node-static

To start the server, run the following command from a terminal in your app
directory:

    node server.js

From your browser, open `localhost:2013`. Open a new tab or window in any
browser and open `localhost:2013` again. To see what's happening, check the
console. In Chrome and Opera, you can access the console through Google Chrome
Developer Tools with `Ctrl+Shift+J` (or `Command+Option+J` on Mac).

Whatever approach you choose for signaling, your backend and client app need to
provide services similar to this example.

### Signal gotchas

- `RTCPeerConnection` won't start gathering candidates until `setLocalDescription()` is called. This is mandated in the [JSEP IETF draft](https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-03#section-4.2.4).
- Take advantage of Trickle ICE. Call `addIceCandidate()` as soon as candidates arrive.

### Ready-made signal servers

If you don't want to roll your own, there are several WebRTC signaling servers
available, which use Socket.IO like the previous example and are integrated with
WebRTC client JavaScript libraries:

- [webRTC.io](https://github.com/webRTC/webRTC.io) is one of the first abstraction libraries for WebRTC.
- [Signalmaster](https://github.com/andyet/signalmaster) is a signaling server created for use with the [SimpleWebRTC](https://github.com/HenrikJoreteg/SimpleWebRTC) JavaScript client library.

If you don't want to write any code at all, complete commercial WebRTC platforms
are available from companies, such as [vLine](https://www.vline.com/),
[OpenTok](https://tokbox.com/opentok), and
[Asterisk](https://wiki.asterisk.org/wiki/display/AST/Asterisk+WebRTC+Support).

For the record, Ericsson built a [signaling server using PHP on Apache](https://labs.ericsson.com/blog/a-web-rtc-tutorial)
in the early days of WebRTC. This is now somewhat obsolete, but it's worth
looking at the code if you're considering something similar.

### Signal security

Encryption is [mandatory](https://www.ietf.org/proceedings/82/slides/rtcweb-13.pdf)
for all WebRTC components.

However, signaling mechanisms aren't defined by WebRTC standards, so it's up to
you to make signaling secure. If an attacker manages to hijack signaling, they
can stop sessions, redirect connections, and record, alter, or inject content.

The most important factor in securing signaling is to use secure protocols:
HTTPS and WSS, such as TLS. This makes sure that messages cannot be intercepted
unencrypted. Also, be careful not to broadcast signaling messages in a way that
they can be accessed by other callers using the same signaling server.

> [!NOTE]
> **Note:** To secure a WebRTC app, signaling should always use [TLS](https://wikipedia.org/wiki/Transport_Layer_Security).

## Cope with NATs and firewalls

For metadata signaling, WebRTC apps use an intermediary server, but for actual
media and data streaming once a session is established, `RTCPeerConnection`
attempts to connect clients directly or peer-to-peer.

In a simpler world, every WebRTC endpoint would have a unique address that it
could exchange with other peers in order to communicate directly.
![Simple peer to peer connection](https://web.dev/static/articles/webrtc-infrastructure/image/simple-peer-peer-connect-3f842ddde7341.png) A world without NATs and firewalls

In reality, most devices live behind one or more layers of [NAT](https://www.howstuffworks.com/nat.htm), some have antivirus software that blocks certain ports and protocols, and many are behind proxies and corporate firewalls. A firewall and NAT may in fact be implemented by the same device, such as a home WIFI router.
![Peers behind NATs and firewalls](https://web.dev/static/articles/webrtc-infrastructure/image/peers-behind-nats-firewa-73717e8affc75.png) The real world

WebRTC apps can use the [ICE](https://en.wikipedia.org/wiki/Interactive_Connectivity_Establishment)
framework to overcome the complexities of real-world networking. To enable this,
your app must pass ICE server URLs to `RTCPeerConnection`.

ICE tries to find the best path to connect peers. It tries all possibilities in
parallel and chooses the most efficient option that works. ICE first tries to
make a connection using the host address obtained from a device's operating
system and network card. If that fails (which it will for devices behind NATs),
ICE obtains an external address using a STUN server and, if that fails, traffic
is routed through a TURN relay server.

In other words, a STUN server is used to get an external network address and
TURN servers are used to relay traffic if direct (peer-to-peer) connection
fails.

Every TURN server supports STUN. A TURN server is a STUN server with additional
built-in relaying functionality. ICE also copes with the complexities of NAT
setups. In reality, NAT hole-punching may require more than just a public
IP:port address.

URLs for STUN and TURN servers are (optionally) specified by a WebRTC app in the
`iceServers` configuration object that is the first argument to the
`RTCPeerConnection` constructor. For [appr.tc](https://appr.tc), that value
looks like this:

    {
      'iceServers': [
        {
          'urls': 'stun:stun.l.google.com:19302'
        },
        {
          'urls': 'turn:192.158.29.39:3478?transport=udp',
          'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
          'username': '28224511:1379330808'
        },
        {
          'urls': 'turn:192.158.29.39:3478?transport=tcp',
          'credential': 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
          'username': '28224511:1379330808'
        }
      ]
    }

> [!NOTE]
> **Note:** The TURN credentials example was time-limited and expired in September 2013.

Once `RTCPeerConnection` has that information, the ICE magic happens
automatically. `RTCPeerConnection` uses the ICE framework to work out the best
path between peers, working with STUN and TURN servers as necessary.

### STUN

[NATs](https://www.howstuffworks.com/nat.htm) provide a device with an IP
address for use within a private local network, but this address can't be used
externally. Without a public address, there's no way for WebRTC peers to
communicate. To get around this problem, WebRTC uses
[STUN](https://wikipedia.org/wiki/STUN).

STUN servers live on the public internet and have one task: check the IP:port
address of an incoming request (from an app running behind a NAT) and send that
address back as a response. In other words, the app uses a STUN server to
discover its IP:port from a public perspective. This process enables a WebRTC
peer to get a publicly accessible address for itself and then pass it to another
peer through a signaling mechanism in order to set up a direct link.

STUN servers don't have to do much or remember much, so relatively low-spec STUN
servers can handle a large number of requests.

Most WebRTC calls successfully make a connection using STUN, though it may
be fewer for calls between peers behind firewalls and complex NAT
configurations.
![Peer to peer connection using a STUN server](https://web.dev/static/articles/webrtc-infrastructure/image/peer-peer-connection-usi-6a294f710d39e.png) Using STUN servers to get public IP:port addresses

### TURN

`RTCPeerConnection` tries to set up direct communication between peers over UDP.
If that fails, `RTCPeerConnection` resorts to TCP. If that fails, TURN servers
can be used as a fallback, relaying data between endpoints.

Just to reiterate, TURN is used to relay audio, video, and data streaming
between peers, not signaling data!

TURN servers have public addresses, so they can be contacted by peers even if
the peers are behind firewalls or proxies. TURN servers have one task: to
relay a stream. Unlike STUN servers, they inherently consume a lot of bandwidth.
![Peer to peer connection using a STUN server](https://web.dev/static/articles/webrtc-infrastructure/image/peer-peer-connection-usi-b844896cb100c.png) The full Monty: STUN, TURN, and signaling

This diagram shows TURN in action. Pure STUN didn't succeed, so each peer
resorts to using a TURN server.

### Deploying STUN and TURN servers

For testing, Google runs a public STUN server, `stun.l.google.com:19302`, as
used by [appr.tc](https://appr.tc). For a production STUN/TURN service, use the
rfc5766-turn-server. Source code for STUN and TURN servers is available on
[GitHub](https://github.com/coturn/rfc5766-turn-server/), where you can also
find links to several sources of information about server installation. A
[VM image for Amazon Web Services](https://groups.google.com/forum/#!msg/discuss-webrtc/X-OeIUC0efs/XW5Wf7Tt1vMJ) is also available.

An alternative TURN server is restund, available as
[source code](https://www.creytiv.com/restund.html) and also for AWS. You can
set up a restund on Compute :

1. Open firewall as necessary for tcp=443, udp/tcp=3478.
2. Create four instances, one for each public IP, Standard Ubuntu 12.06 image.
3. Set up local firewall config (allow ANY from ANY).
4. Install tools: `shell
   sudo apt-get install make
   sudo apt-get install gcc`
5. Install libre from [creytiv.com/re.html](https://creytiv.com/re.html).
6. Fetch restund from [creytiv.com/restund.html](https://creytiv.com/restund.html) and unpack.
7. `wget` [hancke.name/restund-auth.patch](https://hancke.name/restund-auth.patch) and apply with `patch -p1 < restund-auth.patch`.
8. Run `make`, `sudo make install` for libre and restund.
9. Adapt `restund.conf` to your needs (replace IP addresses and make sure it contains the same shared secret) and copy to `/etc`.
10. Copy `restund/etc/restund` to `/etc/init.d/`.
11. Configure restund:
    1. Set `LD_LIBRARY_PATH`.
    2. Copy `restund.conf` to `/etc/restund.conf`.
    3. Set `restund.conf` to use the right 10. IP address.
12. Run restund
13. Test using stund client from remote machine: `./client IP:port`

## Multi-party WebRTC

You may also want to take a look at Justin Uberti's proposed IETF standard for a
[REST API for access to TURN Services](https://tools.ietf.org/html/draft-uberti-rtcweb-turn-rest-00).

There are numerous use cases for media streaming that go beyond a one-to-one
call. For example, video conferencing between a group of colleagues or a public
event with one speaker and hundreds or millions of viewers.

A WebRTC app can use multiple RTCPeerConnections so that every endpoint connects
to every other endpoint in a mesh configuration. This is the approach taken by
apps, such as [talky.io](https://talky.io), and works remarkably well for a
small handful of peers. Beyond that, processing and bandwidth consumption
becomes excessive, especially for mobile clients.
![Mesh: small N-way call](https://web.dev/static/articles/webrtc-infrastructure/image/mesh-small-n-call-d5f302e7a70fd.png) Full mesh topology: Everyone connected to everyone

Alternatively, a WebRTC app could choose one endpoint to distribute streams to
all others in a star configuration. It would also be possible to run a WebRTC
endpoint on a server and construct your own redistribution mechanism (a [sample client app](https://code.google.com/p/webrtc/source/browse/#svn%2Ftrunk%2Ftalk) is provided by webrtc.org).

A `MediaStream` from one `RTCPeerConnection` can be used as the input for
another. This can enable more flexible architectures because it enables a web
app to handle call-routing by choosing which other peer to connect to. To see
this in action, see [WebRTC samples Peer connection relay](https://webrtc.github.io/samples/src/content/peerconnection/multiple-relay/)
and [WebRTC samples Multiple peer connections](https://webrtc.github.io/samples/src/content/peerconnection/multiple/).

### Multipoint control unit

A better option for a large number of endpoints is to use a
[multipoint control unit](https://en.wikipedia.org/wiki/Multipoint_control_unit)
(MCU). This is a server that works as a bridge to distribute media between a
large number of participants. MCUs can cope with different resolutions, codecs,
and frame rates in a video conference; handle transcoding; do selective stream
forwarding; and mix or record audio and video. For multiparty calls, there are a
number of issues to consider, particularly how to display multiple video inputs
and mix audio from multiple sources. Cloud platforms, such as
[vLine](https://www.vline.com/), also attempt to optimize traffic routing.

It's possible to buy a complete MCU hardware package or build your own.
![Rear view of Cisco MCU5300](https://web.dev/static/articles/webrtc-infrastructure/image/rear-view-cisco-mcu5300-9a81f10107d59.png) The back of a [Cisco MCU](https://cisco.com/en/US/products/ps12283)

Several open source MCU software options are available. For example,
[Licode](https://lynckia.com/) (previously known as Lynckia) produces an open
source MCU for WebRTC. OpenTok has [Mantis](https://www.tokbox.com/blog/mantis-next-generation-cloud-technology-for-webrtc/).

## Beyond browsers: VoIP, telephones, and messaging

The standardized nature of WebRTC makes it possible to establish communication
between a WebRTC app running in a browser and a device or platform running on
another communication platform, such as a telephone or a video-conferencing
system.

[SIP](https://en.wikipedia.org/wiki/Session_Initiation_Protocol) is a signaling
protocol used by VoIP and video-conferencing systems. To enable communication
between a WebRTC web app and a SIP client, such as a video-conferencing system,
WebRTC needs a proxy server to mediate signaling. Signaling must flow through
the gateway but, once communication has been established, SRTP traffic (video
and audio) can flow peer-to-peer.

The [Public Switched Telephone Network (PSTN)](https://wikipedia.org/wiki/Public_switched_telephone_network)
is the [circuit-switched](https://wikipedia.org/wiki/Circuit_switching) network
of all "plain old" analog telephones. For calls between WebRTC web apps and
telephones, traffic must go through a PSTN gateway. Likewise, WebRTC web apps
need an intermediary XMPP server to communicate with
[Jingle](https://wikipedia.org/wiki/Jingle_(protocol)) endpoints such as IM
clients. Jingle was developed by Google as an extension to XMPP to enable
voice and video for messaging services. Current WebRTC implementations are
based on the C++ [libjingle](https://developers.google.com/talk/libjingle/developer_guide)
library, an implementation of Jingle initially developed for Talk.

A number of apps, libraries, and platforms make use of WebRTC's ability to
communicate with the outside world. For example:

- [jsSIP](https://www.jssip.net/): JavaScript SIP library
- [Phono](https://phono.com/): open source JavaScript phone API built as a plugin
- [Twilio](https://www.twilio.com/): voice and messaging

## Find out more

- Read the [WebRTC API documentation](https://developer.mozilla.org/docs/Web/API/WebRTC_API).
- The [WebRTC codelab](https://bitbucket.org/webrtc/codelab) provides step-by-step instructions for how to build a video and text chat app using a Socket.io signaling service running on Node.
- Chris Wilson's SFHTML5 presentation: [Introduction to WebRTC Apps](https://www.youtube.com/watch?v=3Ifbqaw5l_I)

The 350-page book [WebRTC: APIs and RTCWEB Protocols of the HTML5 Real-Time Web](https://webrtcbook.com)
provides a lot of detail about data and signaling pathways, and includes a
number of detailed network topology diagrams.