Back-End-Dienste für WebRTC-App erstellen

Was ist Signalisierung?

Signaling ist der Prozess der Koordination der Kommunikation. Damit eine WebRTC-App einen Anruf einrichten kann, müssen ihre Clients die folgenden Informationen austauschen:

  • Nachrichten zur Sitzungssteuerung zum Öffnen oder Schließen der Kommunikation
  • Fehlermeldungen
  • Medienmetadaten wie Codecs, Codec-Einstellungen, Bandbreite und Medientypen
  • Wichtige Daten zum Herstellen sicherer Verbindungen
  • Netzwerkdaten, z. B. die IP-Adresse und der Port eines Hosts aus Sicht der Außenwelt

Dieser Signalisierungsprozess benötigt eine Möglichkeit für Clients, Nachrichten hin- und herzusenden. Dieser Mechanismus wird von den WebRTC APIs nicht implementiert. Sie müssen es selbst erstellen. Weiter unten in diesem Artikel erfahren Sie, wie Sie einen Signalisierungsdienst erstellen. Zuerst benötigen Sie jedoch ein wenig Kontext.

Warum wird die Signalisierung nicht von WebRTC definiert?

Signalisierungsmethoden und -protokolle sind nicht durch WebRTC-Standards spezifiziert, um Redundanz zu vermeiden und die Kompatibilität mit etablierten Technologien zu maximieren. Dieser Ansatz wird im JavaScript Session Einrichtung Protocol (JSEP) beschrieben:

Die JSEP-Architektur vermeidet außerdem, dass ein Browser den Zustand speichern muss, d. h. als Signalisierungsmaschine fungiert. Dies wäre zum Beispiel problematisch, wenn bei jedem Neuladen einer Seite Signaldaten verloren gingen. Stattdessen kann der Signalisierungsstatus auf einem Server gespeichert werden.

JSEP-Architekturdiagramm
JSEP-Architektur

JSEP erfordert den Austausch der oben genannten Medienmetadaten zwischen Peers von offer und answer. Angebote und Antworten werden im SDP-Format (Session Description Protocol) übermittelt, das so aussieht:

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
…

Möchtest du wissen, was all diese SDP-Schweinchen eigentlich bedeuten? Sehen Sie sich die Beispiele der Internet Engineering Task Force (IETF) an.

Beachten Sie, dass WebRTC so konzipiert ist, dass das Angebot oder die Antwort optimiert werden kann, bevor sie als lokale oder Remote-Beschreibung festgelegt werden. Dazu bearbeiten Sie die Werte im SDP-Text. Beispielsweise kannst du mit der Funktion preferAudioCodec() in appr.tc den Standard-Codec und die Bitrate festlegen. SDP lässt sich mit JavaScript etwas mühsam manipulieren und es wird darüber gesprochen, ob zukünftige Versionen von WebRTC stattdessen JSON verwenden sollten. Es gibt jedoch einige Vorteile, sich bei SDP zu halten.

RTCPeerConnection API und Signalisierung: Angebot, Antwort und Kandidaten

RTCPeerConnection ist die API, die von WebRTC-Apps verwendet wird, um eine Verbindung zwischen Peers herzustellen und Audio- und Videoinhalte zu kommunizieren.

Zum Initialisieren dieses Prozesses hat RTCPeerConnection zwei Aufgaben:

  • Ermitteln Sie lokale Medienbedingungen wie Auflösung und Codec-Fähigkeiten. Dies sind die Metadaten, die für den Angebot-und-Antwort-Mechanismus verwendet werden.
  • Ermitteln Sie potenzielle Netzwerkadressen für den Host der App, sogenannte Kandidaten.

Sobald diese lokalen Daten ermittelt wurden, müssen sie über einen Signalisierungsmechanismus mit dem Remote-Peer ausgetauscht werden.

Stell dir vor, Alice möchte Eve anrufen. Hier ist das vollständige Angebot/die Antwort mit allen Details:

  1. Alice erstellt ein RTCPeerConnection-Objekt.
  2. Alice erstellt ein Angebot (eine SDP-Sitzungsbeschreibung) mit der Methode RTCPeerConnection createOffer().
  3. Anne ruft setLocalDescription() mit ihrem Angebot an.
  4. Alice ordnet das Angebot an und sendet es über einen Signalmechanismus an Eve.
  5. Eve ruft setRemoteDescription() mit dem Angebot von Anne an, damit ihre RTCPeerConnection etwas über Annes Einrichtung erfährt.
  6. Eve ruft createAnswer() auf und dem Erfolgs-Callback dafür wird eine lokale Sitzungsbeschreibung übergeben – Eves Antwort.
  7. Eve legt ihre Antwort als lokale Beschreibung fest, indem sie setLocalDescription() aufruft.
  8. Eve verwendet dann den Signalmechanismus, um Alice ihre Antwort mit String zu senden.
  9. Alice legt mithilfe von setRemoteDescription() die Antwort von Eve als Beschreibung der Remote-Sitzung fest.

Außerdem müssen Alice und Eve Netzwerkinformationen austauschen. Der Begriff „Kandidaten finden“ bezieht sich auf den Prozess der Suche nach Netzwerkschnittstellen und Ports mit dem ICE-Framework.

  1. Alice erstellt ein RTCPeerConnection-Objekt mit einem onicecandidate-Handler.
  2. Der Handler wird aufgerufen, sobald Netzwerkkandidaten verfügbar werden.
  3. Im Handler sendet Alice über ihren Signalisierungskanal verkettete Kandidatendaten an Eve.
  4. Wenn Eve eine Kandidatennachricht von Alice erhält, ruft sie addIceCandidate() auf, um den Kandidaten der Remote-Peer-Beschreibung hinzuzufügen.

JSEP unterstützt das ICE Candidate Trickling. Damit können dem Anrufer nach dem ersten Angebot nach und nach Kandidaten vorgeschlagen werden. Der Aufgerufene kann dann mit der Bearbeitung des Anrufs beginnen und eine Verbindung herstellen, ohne auf das Eintreffen aller Kandidaten warten zu müssen.

WebRTC für Signalisierung

Das folgende Code-Snippet ist ein W3C-Codebeispiel, in dem der vollständige Signalisierungsprozess zusammengefasst wird. Der Code geht davon aus, dass der Signalisierungsmechanismus SignalingChannel vorhanden ist. Die Signalisierung wird später noch ausführlicher behandelt.

// 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);
  }
};

Unter simpl.info RTCPeerConnection können Sie die Prozesse für Angebote/Antworten und Kandidatenaustausch in Aktion sehen. Im Konsolenprotokoll finden Sie ein Beispiel für einen einseitigen Videochat. Wenn Sie weitere Informationen benötigen, laden Sie eine vollständige Dump-Datei mit WebRTC-Signalen und -Statistiken von der Seite about://webrtc-internals in Google Chrome oder der Seite opera://webrtc-internals in Opera herunter.

Peer-Erkennung

Du kannst beispielsweise fragen: „Wie finde ich jemanden, mit dem ich sprechen kann?“

Für Telefonanrufe gibt es Telefonnummern und Verzeichnisse. Für Online-Videochats und -Messaging benötigen Sie Identitäts- und Anwesenheitsmanagementsysteme sowie eine Möglichkeit für Nutzer, Sitzungen zu initiieren. WebRTC-Apps benötigen eine Möglichkeit für Clients, sich gegenseitig zu signalisieren, dass sie einen Anruf starten oder daran teilnehmen möchten.

Peer-Erkennungsmechanismen werden nicht von WebRTC definiert und Sie gehen hier nicht auf die Optionen ein. Der Vorgang kann einfach per E-Mail oder Messaging an eine URL erfolgen. Bei Videoanruf-Apps wie Talky, tawk.to und Browser Meeting können Sie Personen zu einem Anruf einladen, indem Sie einen benutzerdefinierten Link teilen. Der Entwickler Chris Ball hat ein spannendes serverless-webrtc-Experiment entwickelt, mit dem WebRTC-Anrufteilnehmer Metadaten über beliebige Messaging-Dienste wie IM, E-Mail oder Taube austauschen können.

Wie können Sie einen Signalisierungsdienst erstellen?

Zur Erinnerung: Signalprotokolle und -mechanismen sind nicht durch WebRTC-Standards definiert. Für welche Option Sie sich auch entscheiden, Sie benötigen einen Zwischenserver, um Signalnachrichten und App-Daten zwischen Clients auszutauschen. Leider kann eine Webanwendung nicht einfach ins Internet rufen: „Verbindung zu meinem Freund herstellen!“

Zum Glück sind diese Nachrichten klein und werden meist zu Beginn eines Anrufs ausgetauscht. Beim Testen mit appr.tc für eine Videochatsitzung wurden vom Signalisierungsdienst insgesamt etwa 30–45 Nachrichten mit einer Gesamtgröße von etwa 10 KB verarbeitet.

WebRTC-Signalisierungsdienste sind nicht nur relativ einfach in Bezug auf die Bandbreite, sondern verbrauchen auch nicht viel Verarbeitungs- oder Speicherbedarf, da sie nur Nachrichten weiterleiten und nur eine kleine Menge an Sitzungsstatusdaten speichern müssen, z. B. welche Clients verbunden sind.

Nachrichten vom Server an den Client senden

Ein Nachrichtendienst für die Signalisierung muss bidirektional sein: von Client zu Server und von Server zu Client. Die bidirektionale Kommunikation unterliegt dem Anfrage-/Antwortmodell von HTTP-Client/Server, aber verschiedene Hacks wie lange Polling wurden über viele Jahre entwickelt, um Daten von einem auf einem Webserver ausgeführten Dienst an eine Webanwendung zu übertragen, die in einem Browser ausgeführt wird.

In letzter Zeit wurde die EventSource API allgemein implementiert. Dadurch werden vom Server gesendete Ereignisse ermöglicht, also Daten, die über HTTP von einem Webserver an einen Browser-Client gesendet werden. EventSource ist für unidirektionale Nachrichten vorgesehen, kann aber in Kombination mit XHR verwendet werden, um einen Dienst für den Austausch von Signalnachrichten zu erstellen. Ein Signalisierungsdienst leitet eine Nachricht von einem Aufrufer, die per XHR-Anfrage übermittelt wurde, über EventSource an den Aufgerufenen weiter.

WebSocket ist eine natürlichere Lösung, die für die vollständige Duplex-Client-Server-Kommunikation entwickelt wurde, d. h. Nachrichten, die gleichzeitig in beide Richtungen übertragen werden können. Ein Vorteil eines Signalisierungsdienstes, der mit reinem WebSocket oder vom Server gesendeten Ereignissen erstellt wurde (EventSource) besteht darin, dass das Back-End für diese APIs auf einer Vielzahl von Web-Frameworks implementiert werden kann, die bei den meisten Webhosting-Paketen für Sprachen wie PHP, Python und Ruby üblich sind.

Alle modernen Browser außer Opera Mini unterstützen WebSocket. Und, noch wichtiger, alle Browser, die WebRTC unterstützen, unterstützen WebSocket auch auf Desktop-Computern und Mobilgeräten. Für alle Verbindungen sollte TLS verwendet werden, damit Nachrichten nicht unverschlüsselt abgefangen werden können und um Probleme beim Proxydurchlauf zu reduzieren. Weitere Informationen zu WebSocket und Proxydurchläufen finden Sie im Kapitel zu WebRTC im Video High Performance Browser Networking von Ilya Grigorik.

Es ist auch möglich, die Signalisierung zu verarbeiten, indem WebRTC-Clients dazu bringen, einen Messaging-Server wiederholt über Ajax abzufragen. Dies führt jedoch zu vielen redundanten Netzwerkanfragen, was für Mobilgeräte besonders problematisch ist. Auch nach dem Aufbau einer Sitzung müssen Peers bei Änderungen oder Beendigung der Sitzung durch andere Peers Signale abfragen. In der Beispiel-App WebRTC Book wird diese Option mit einigen Optimierungen der Abfragehäufigkeit verwendet.

Skalierungssignal

Obwohl ein Signalisierungsdienst pro Client relativ wenig Bandbreite und CPU in Anspruch nimmt, müssen Signalisierungsserver für eine beliebte Anwendung viele Nachrichten von verschiedenen Standorten mit hoher Gleichzeitigkeit verarbeiten. WebRTC-Apps mit hohem Traffic erfordern Signalisierungsserver, die eine beträchtliche Last bewältigen können. Sie gehen hier nicht ins Detail, aber es gibt eine Reihe von Optionen für hochvolumige, leistungsstarke Werbebotschaften, einschließlich der folgenden:

  • eXtensible Messaging and Presence Protocol (XMPP), ursprünglich als Jabber-Protokoll bezeichnet, wurde für Instant Messaging entwickelt und für die Signalisierung verwendet. Serverimplementierungen umfassen ejabberd und Openfire. JavaScript-Clients wie Strophe.js verwenden BOSH, um bidirektionales Streaming zu emulieren. Aus verschiedenen Gründen ist BOSH jedoch möglicherweise nicht so effizient wie WebSocket und aus den gleichen Gründen nicht gut skaliert.) Jingle ist eine XMPP-Erweiterung zur Aktivierung von Sprach- und Videofunktionen. Im WebRTC-Projekt werden Netzwerk- und Transportkomponenten aus der Bibliothek libjingle verwendet, einer C++-Implementierung von Jingle.

  • Open-Source-Bibliotheken wie ZeroMQ (wie von TokBox für den Dienst Rumour verwendet) und OpenMQ (NullMQ) wendet ZeroMQ-Konzepte auf Webplattformen an, die das STOMP-Protokoll über WebSocket verwenden.

  • Kommerzielle Cloud-Messaging-Plattformen, die WebSocket verwenden (auch wenn sie auf langes Polling zurückgreifen können), wie Pusher, Kaazing und PubNub (PubNub hat auch eine API für WebRTC).

  • Kommerzielle WebRTC-Plattformen wie vLine

Im Real-Time Web Technologies Guide von Entwickler Phil Leggetter finden Sie eine umfassende Liste von Messaging-Diensten und -Bibliotheken.

Signalisierungsdienst mit Socket.io auf Node erstellen

Im Folgenden finden Sie Code für eine einfache Webanwendung, die einen mit Socket.io auf Node erstellten Signalisierungsdienst verwendet. Das Design von Socket.io macht es einfach, einen Dienst zum Austausch von Nachrichten zu erstellen. Socket.io eignet sich aufgrund des integrierten Konzepts von Räumen besonders für die WebRTC-Signalisierung. Dieses Beispiel ist nicht für die Skalierung als produktionsreife Signalisierungsdienst konzipiert, ist aber für eine relativ kleine Anzahl von Nutzern leicht verständlich.

Socket.io verwendet WebSocket mit Fallbacks: AJAX Long Polling, mehrteiliges AJAX Streaming, Forever iFrame und JSONP Polling. Sie wurde auf verschiedene Back-Ends übertragen, ist aber vielleicht am besten für die in diesem Beispiel verwendete Knotenversion bekannt.

In diesem Beispiel wird kein WebRTC verwendet. Sie soll nur zeigen, wie eine Signalisierung in eine Webanwendung eingebunden wird. Im Konsolenprotokoll können Sie sehen, was passiert, wenn Kunden einem Chatroom beitreten und Nachrichten austauschen. In diesem WebRTC-Codelab finden Sie eine detaillierte Anleitung dazu, wie Sie die Funktion in eine vollständige WebRTC-App für Videoanrufe einbinden.

Dies ist der 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>

Hier sehen Sie die JavaScript-Datei main.js, auf die im Client verwiesen wird:

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);
});

Hier ist die vollständige 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);

  });

});

(Sie müssen sich dazu nicht mit node-static vertraut machen. Es kommt in diesem Beispiel einfach vor.)

Um diese Anwendung auf localhost auszuführen, müssen Node, Socket.IO und node-static installiert sein. Node.js kann von Node.js heruntergeladen werden und lässt sich schnell und einfach installieren. Führen Sie Node Package Manager über ein Terminal in Ihrem Anwendungsverzeichnis aus, um Socket.IO und node-static zu installieren:

npm install socket.io
npm install node-static

Führen Sie den folgenden Befehl über ein Terminal in Ihrem App-Verzeichnis aus, um den Server zu starten:

node server.js

Öffnen Sie localhost:2013 in Ihrem Browser. Öffnen Sie in einem beliebigen Browser einen neuen Tab oder ein neues Fenster und öffnen Sie localhost:2013 noch einmal. In der Konsole können Sie sehen, was passiert. In Chrome und Opera können Sie über die Google Chrome-Entwicklertools mit Ctrl+Shift+J (oder Command+Option+J auf dem Mac) auf die Konsole zugreifen.

Unabhängig davon, welchen Ansatz Sie für die Signalisierung wählen, müssen Ihr Back-End und Ihre Clientanwendung zumindest Dienste wie in diesem Beispiel bereitstellen.

Tipps für die Signalisierung

  • RTCPeerConnection beginnt erst dann mit dem Zusammentragen von Kandidaten, wenn setLocalDescription() aufgerufen wurde. Dies ist im JSEP-IETF-Entwurf vorgesehen.
  • Nutze die Vorteile von Trickle ICE. Rufen Sie addIceCandidate() an, sobald die Kandidaten eintreffen.

Fertige Signalisierungsserver

Es gibt mehrere WebRTC-Signalisierungsserver, die Socket.IO wie im vorherigen Beispiel verwenden und in die WebRTC-Client-JavaScript-Bibliotheken integriert sind:

  • webRTC.io ist eine der ersten Abstraktionsbibliotheken für WebRTC.
  • Signalmaster ist ein Signalisierungsserver, der für die Verwendung mit der JavaScript-Clientbibliothek SimpleWebRTC entwickelt wurde.

Wenn Sie überhaupt keinen Code schreiben möchten, können Sie vollständige kommerzielle WebRTC-Plattformen von Unternehmen wie vLine, OpenTok und Asterisk nutzen.

In den Anfängen von WebRTC hat Ericsson einen Signalisierungsserver mit PHP auf Apache erstellt. Dieser Code ist inzwischen etwas veraltet, es lohnt sich jedoch, sich den Code anzusehen, wenn Sie etwas Ähnliches in Betracht ziehen.

Signalsicherheit

„Sicherheit ist die Kunst, nichts passieren zu lassen.“

Salman Rushdie

Die Verschlüsselung ist für alle WebRTC-Komponenten obligatorisch.

Signalmechanismen sind jedoch nicht durch WebRTC-Standards definiert. Sie müssen dafür sorgen, dass die Signalisierung sicher ist. Wenn es einem Angreifer gelingt, Signale zu hacken, kann er Sitzungen beenden, Verbindungen umleiten und Inhalte aufzeichnen, ändern oder einschleusen.

Der wichtigste Faktor für die Signalisierung ist die Verwendung sicherer Protokolle – HTTPS und WSS (z. B. TLS), die dafür sorgen, dass Nachrichten nicht unverschlüsselt abgefangen werden können. Achten Sie außerdem darauf, Signalnachrichten nicht so zu übertragen, dass andere Anrufer über denselben Signalisierungsserver darauf zugreifen können.

Nach der Signalisierung: ICE für den Umgang mit NATs und Firewalls verwenden

Für die Metadaten-Signalisierung verwenden WebRTC-Apps einen Zwischenserver. Für das tatsächliche Medien- und Datenstreaming nach dem Aufbau einer Sitzung versucht RTCPeerConnection jedoch, Clients direkt oder über Peer-to-Peer zu verbinden.

In einer einfacheren Welt hätte jeder WebRTC-Endpunkt eine eindeutige Adresse, die er mit anderen Peers austauschen könnte, um direkt zu kommunizieren.

Einfache Peer-to-Peer-Verbindung
Eine Welt ohne NATs und Firewalls

In Wirklichkeit befinden sich die meisten Geräte hinter einer oder mehreren NAT-Schichten. Einige haben eine Antivirensoftware, die bestimmte Ports und Protokolle blockiert, und viele befinden sich hinter Proxys und Unternehmensfirewalls. Eine Firewall und NAT können tatsächlich von demselben Gerät, z. B. einem WLAN-Router zu Hause, implementiert werden.

Peers hinter NATs und Firewalls
Die Realität

WebRTC-Apps können das ICE-Framework verwenden, um die Komplexitäten von realen Netzwerken zu bewältigen. Dazu muss deine App ICE-Server-URLs an RTCPeerConnection übergeben, wie in diesem Artikel beschrieben.

ICE versucht, die beste Verbindung für Gleichgesinnte zu finden. Alle Möglichkeiten werden parallel getestet und es wird die effizienteste Option ausgewählt. ICE versucht zuerst, eine Verbindung über die Hostadresse des Betriebssystems und der Netzwerkkarte des Geräts herzustellen. Wenn das nicht funktioniert (was bei Geräten hinter NATs der Fall ist), erhält ICE über einen STUN-Server eine externe Adresse. Falls dieser ausfällt, wird der Traffic über einen turn-Relay-Server geleitet.

Mit anderen Worten: Ein STUN-Server wird verwendet, um eine externe Netzwerkadresse abzurufen, und turn-Server, um Traffic weiterzuleiten, wenn die direkte Verbindung (Peer-to-Peer) fehlschlägt.

Jeder Turn-Server unterstützt STUN. Ein Turn-Server ist ein STUN-Server mit zusätzlichen integrierten Weiterleitungsfunktionen. ICE bewältigt auch die Komplexität von NAT-Einrichtungen. In Wirklichkeit ist für das NAT-Loch-Punching möglicherweise mehr als nur eine öffentliche IP-Port-Adresse erforderlich.

URLs für STUN- und/oder Turn-Server werden (optional) von einer WebRTC-App im iceServers-Konfigurationsobjekt angegeben, das das erste Argument für den RTCPeerConnection-Konstruktor ist. Für appr.tc sieht dieser Wert so aus:

{
  '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'
    }
  ]
}

Sobald RTCPeerConnection diese Informationen hat, geschieht der Vorgang automatisch. RTCPeerConnection verwendet das ICE-Framework, um den besten Weg zwischen Peers zu finden, und arbeitet bei Bedarf mit STUN- und Turn-Servern zusammen.

STUNS

NATs stellen einem Gerät eine IP-Adresse zur Verwendung in einem privaten lokalen Netzwerk bereit. Diese Adresse kann jedoch nicht extern genutzt werden. Ohne eine öffentliche Adresse gibt es keine Möglichkeit für WebRTC-Peers, zu kommunizieren. Um dieses Problem zu umgehen, verwendet WebRTC STUN.

STUN-Server befinden sich im öffentlichen Internet und haben nur eine einfache Aufgabe: Prüfen Sie die IP:Port-Adresse einer eingehenden Anfrage (von einer Anwendung, die hinter einer NAT ausgeführt wird) und senden Sie diese Adresse als Antwort zurück. Mit anderen Worten:Die Anwendung verwendet einen STUN-Server, um seinen IP-Port aus öffentlicher Perspektive zu erkennen. Dieser Prozess ermöglicht es einem WebRTC-Peer, eine öffentlich zugängliche Adresse für sich selbst zu erhalten und diese dann über einen Signalisierungsmechanismus an einen anderen Peer weiterzugeben, um eine direkte Verbindung einzurichten. (In der Praxis funktionieren verschiedene NATs auf unterschiedliche Weise und es kann mehrere NAT-Ebenen geben, aber das Prinzip ist immer noch dasselbe.)

STUN-Server müssen nicht viel tun oder sich viel merken, sodass STUN-Server mit relativ niedrigen Spezifikationen eine große Anzahl von Anfragen verarbeiten können.

Die meisten WebRTC-Aufrufe stellen erfolgreich eine Verbindung über STUN her – laut Webrtcstats.com 86 %. Bei Aufrufen zwischen Peers hinter Firewalls und komplexen NAT-Konfigurationen kann dies weniger sein.

Peer-to-Peer-Verbindung über einen STUN-Server
Öffentliche IP-Port-Adressen mithilfe von STUN-Servern abrufen

TURN

RTCPeerConnection versucht, eine direkte Kommunikation zwischen Peers über UDP einzurichten. Wenn dies fehlschlägt, verwendet RTCPeerConnection TCP. Wenn das nicht funktioniert, können Turn-Server als Fallback verwendet werden, die Daten zwischen Endpunkten weiterleiten.

Nur zur Wiederholung: Turn wird verwendet, um Audio-, Video- und Datenstreams zwischen Peers weiterzuleiten und nicht Daten zu signalisieren!

turn-Server haben öffentliche Adressen, sodass sie von Peers kontaktiert werden können, auch wenn sich die Peers hinter Firewalls oder Proxys befinden. Es ist eine konzeptionell einfache Aufgabe, einen Stream weiterzuleiten. Im Gegensatz zu STUN-Servern verbrauchen sie jedoch grundsätzlich viel Bandbreite. Mit anderen Worten: Turn-Server müssen kräftiger sein.

Peer-to-Peer-Verbindung über einen STUN-Server
Der vollständige Monty: STUN, DREHEN und Signale

Dieses Diagramm zeigt Turn in Aktion. Pure STUN war nicht erfolgreich, daher verwendet jeder Peer die Nutzung eines Turn-Servers.

STUN- und Turn-Server bereitstellen

Zum Testen führt Google den öffentlichen STUN-Server stun.l.google.com:19302 ein, der von appr.tc verwendet wird. Verwenden Sie für einen STUN/turn-Produktionsdienst den rfc5766-turn-server. Der Quellcode für STUN- und Turn-Server ist auf GitHub verfügbar. Dort finden Sie auch Links zu verschiedenen Informationsquellen zur Serverinstallation. Ein VM-Image für Amazon Web Services ist ebenfalls verfügbar.

Ein alternativer turn-Server ist neu und als Quellcode sowie für AWS verfügbar. Im Folgenden finden Sie Anweisungen zur Einrichtung der Wiederherstellung in Compute Engine.

  1. Öffnen Sie die Firewall nach Bedarf für tcp=443, udp/tcp=3478.
  2. Erstellen Sie vier Instanzen, eine für jede öffentliche IP-Adresse, ein Standard-Ubuntu 12.06-Image.
  3. Richten Sie die Konfiguration der lokalen Firewall ein (BELIEBIGE von ANY zulassen).
  4. Tools installieren: shell sudo apt-get install make sudo apt-get install gcc
  5. Installieren Sie Libre von creytiv.com/re.html.
  6. Rufen Sie den Restund von creytiv.com/restund.html ab und entpacken Sie ihn./
  7. wget hancke.name/restund-auth.patch und anwenden mit patch -p1 < restund-auth.patch.
  8. Führen Sie make, sudo make install für libre und restund aus.
  9. Passen Sie restund.conf an Ihre Anforderungen an (ersetzen Sie die IP-Adressen und achten Sie darauf, dass der gleiche geheime Schlüssel enthalten ist) und kopieren Sie ihn in /etc.
  10. restund/etc/restund in /etc/init.d/ kopieren.
  11. Konfigurieren Sie die Wiederherstellung:
    1. Legen Sie dazu LD_LIBRARY_PATH fest.
    2. restund.conf in /etc/restund.conf kopieren.
    3. Legen Sie restund.conf fest, um die richtigen 10 zu verwenden. IP-Adresse
  12. Lauf erholsam
  13. Mit Stund-Client von Remote-Computer testen: ./client IP:port

Mehr als 1:1: Multiparty WebRTC

Sie können sich auch den von Justin Uberti vorgeschlagenen IETF-Standard für eine REST API für den Zugriff auf Turn-Dienste ansehen.

Es ist leicht vorstellbar, Anwendungsfälle für Mediastreaming zu nutzen, die über ein Einzelgespräch hinausgehen. Das kann beispielsweise eine Videokonferenz mit einer Gruppe von Kollegen oder eine öffentliche Veranstaltung mit einem Sprecher und Hunderten oder Millionen von Zuschauern sein.

Eine WebRTC-App kann mehrere RTCPeerConnections verwenden, sodass jeder Endpunkt eine Verbindung zu jedem anderen Endpunkt in einer Mesh-Konfiguration herstellt. Dies ist der Ansatz, der von Apps wie talky.io verwendet wird, der bei einigen wenigen ähnlichen Apps erstaunlich gut funktioniert. Darüber hinaus wird der Verarbeitungs- und Bandbreitenverbrauch übermäßig hoch, insbesondere bei mobilen Clients.

Mesh-Netzwerk: kleiner N-Wege-Anruf
Full-Mesh-Topologie: Alle sind mit jedem verbunden

Alternativ könnte eine WebRTC-App einen Endpunkt auswählen, um Streams in einer Sternkonfiguration an alle anderen zu verteilen. Sie könnten auch einen WebRTC-Endpunkt auf einem Server ausführen und Ihren eigenen Umverteilungsmechanismus entwickeln. Eine Beispiel-Client-App wird von webrtc.org bereitgestellt.

Seit Chrome 31 und Opera 18 kann ein MediaStream aus einem RTCPeerConnection als Eingabe für ein anderes verwendet werden. Dies ermöglicht flexiblere Architekturen, da eine Webanwendung die Weiterleitung von Anrufen verarbeiten kann. Dazu wird ausgewählt, zu welchem anderen Peer eine Verbindung hergestellt werden soll. Ein Beispiel hierfür finden Sie unter WebRTC-Beispiele für Peer-Verbindungs-Relay und WebRTC-Beispiele Mehrere Peer-Verbindungen.

Mehrpunktsteuerung

Für eine große Anzahl von Endpunkten ist die Verwendung einer Multipoint-Steuerungseinheit (Multipoint Control Unit, MCU) besser geeignet. Dieser Server dient als Brücke für die Verteilung von Medien zwischen einer großen Anzahl von Teilnehmern. MCUs können mit unterschiedlichen Auflösungen, Codecs und Framerates in einer Videokonferenz umgehen, transkodieren, selektive Streamweiterleitung nutzen und Audio und Video mischen oder aufzeichnen. Bei Anrufen mit mehreren Parteien gibt es eine Reihe von Faktoren, die berücksichtigt werden müssen, insbesondere die Anzeige mehrerer Videoeingänge und das Mischen von Audio aus mehreren Quellen. Cloud-Plattformen wie vLine versuchen ebenfalls, das Traffic-Routing zu optimieren.

Es ist möglich, ein komplettes MCU-Hardwarepaket zu kaufen oder ein eigenes zu erstellen.

Rückansicht des Cisco MCU5300
Die Rückseite eines Cisco MCU

Es gibt verschiedene Open-Source-MCU-Softwareoptionen. Zum Beispiel produziert Licode (früher Lynckia) ein Open-Source-MCU für WebRTC. OpenTok verwendet Mantis.

Über Browser hinaus: VoIP, Telefone und Messaging

Durch die Standardisierung von WebRTC kann eine Kommunikation zwischen einer WebRTC-App, die in einem Browser ausgeführt wird, und einem Gerät oder einer Plattform, die auf einer anderen Kommunikationsplattform ausgeführt wird, hergestellt werden, z. B. einem Telefon oder einem Videokonferenzsystem.

SIP ist ein Signalisierungsprotokoll, das von VoIP- und Videokonferenzsystemen verwendet wird. Um die Kommunikation zwischen einer WebRTC-Webanwendung und einem SIP-Client wie einem Videokonferenzsystem zu ermöglichen, benötigt WebRTC einen Proxyserver zur Vermittlung von Signalen. Die Signale müssen durch das Gateway fließen, aber sobald die Kommunikation hergestellt ist, kann der SRTP-Datenverkehr (Video und Audio) direkt über Peer-to-Peer fließen.

Das Public Switched Telephone Network (PSTN) ist das Netzwerk umgeschaltet aller „einfachen alten“ analogen Telefonanlagen. Bei Anrufen zwischen WebRTC-Web-Apps und Telefonen muss der Datenverkehr über ein PSTN-Gateway erfolgen. Ebenso benötigen WebRTC-Webanwendungen einen zwischengeschalteten XMPP-Server, um mit Jingle-Endpunkten wie IM-Clients zu kommunizieren. Jingle wurde von Google als Erweiterung von XMPP entwickelt, um Sprach- und Videofunktionen für Messaging-Dienste zu ermöglichen. Aktuelle WebRTC-Implementierungen basieren auf der C++-Bibliothek libjingle, einer Implementierung von Jingle, die ursprünglich für Talk entwickelt wurde.

Eine Reihe von Apps, Bibliotheken und Plattformen nutzt die WebRTC-Funktion für die Kommunikation mit der Außenwelt:

  • sipML5: ein Open-Source-JavaScript-SIP-Client
  • jsSIP: JavaScript-SIP-Bibliothek
  • Phono: Open-Source-JavaScript-Phone API als Plug-in
  • Zingaya: ein einbettbares Smartphone-Widget
  • Twilio: Sprach- und Messaging-Dienste
  • Uberconference: Videokonferenz

Die sipML5-Entwickler haben auch das webrtc2sip-Gateway erstellt. Tethr und Tropo haben ein Framework für die Katastrophenkommunikation „in einem Aktentaschen“ demonstriert. Dazu wurde eine OpenBTS-Zelle verwendet, um die Kommunikation zwischen Feature-Phones und Computern über WebRTC zu ermöglichen. Das ist Telefonkommunikation ohne Mobilfunkanbieter.

Weitere Informationen

Im WebRTC-Codelab findest du eine Schritt-für-Schritt-Anleitung zum Erstellen einer Video- und Textchat-App mit einem Socket.io-Signalisierungsdienst auf Node.com.

Google I/O WebRTC-Präsentation aus dem Jahr 2013 mit WebRTC Tech Lead, Justin Uberti

Chris Wilson's SFHTML5-Präsentation: Einführung in WebRTC-Apps

Das 350 Seiten umfassende Buch WebRTC: APIs and RTCWEB Protocols of the HTML5 Real-Time Web enthält viele Details über Daten- und Signalpfade und enthält eine Reihe detaillierter Diagramme zur Netzwerktopologie.

WebRTC und Signaling: What Two Years Has Taught Us – TokBox-Blogpost darüber, warum es eine gute Idee war, die Spezifikationen nicht mehr zu verwenden

Der A Practical Guide to Building WebRTC Apps (Ben Strong) zur Erstellung von WebRTC-Apps bietet viele Informationen über WebRTC-Topologien und -Infrastruktur.

Im Kapitel zu WebRTC in Ilya Grigorik High Performance Browser Networking geht es um die WebRTC-Architektur, Anwendungsfälle und Leistung.