Tworzenie usług backendu niezbędnych dla aplikacji WebRTC

Co to jest sygnalizacja?

Sygnalizacja to proces koordynowania komunikacji. Aby aplikacja WebRTC mogła nawiązać połączenie, jej klienci muszą wymieniać się tymi informacjami:

  • Wiadomości sterujące sesją używane do otwierania i zamykania komunikacji
  • Komunikaty o błędach
  • metadane multimediów, takie jak kodeki, ustawienia kodeków, przepustowość i rodzaje multimediów;
  • Kluczowe dane używane do nawiązywania bezpiecznych połączeń
  • Dane sieciowe, takie jak adres IP hosta i port widziane z zewnątrz

Ten proces sygnalizacji wymaga sposobu na przesyłanie wiadomości między klientami. Ten mechanizm nie jest zaimplementowany w interfejsach WebRTC API. Musisz go samodzielnie zbudować. W dalszej części tego artykułu dowiesz się, jak utworzyć usługę sygnalizacyjną. Najpierw jednak musisz poznać kontekst.

Dlaczego sygnalizacja nie jest zdefiniowana przez WebRTC?

Aby uniknąć nadmiarowości i zmaksymalizować zgodność z ustalonymi technologiami, standardy WebRTC nie określają metod i protokołów sygnalizacji. Takie podejście jest opisane w protokole JavaScript Session Establishment Protocol (JSEP):

Architektura JSEP zapobiega też konieczności zapisywania stanu przez przeglądarkę, czyli pełnienia funkcji maszyny stanów sygnalizacyjnych. Byłoby to problematyczne, gdyby na przykład dane sygnalizacyjne były tracone przy każdym ponownym wczytaniu strony. Zamiast tego stan sygnalizacji może być zapisywany na serwerze.

Schemat architektury JSEP
Architektura JSEP

JSEP wymaga wymiany między uczestnikami oferty i odpowiedzi, czyli wspomnianych wyżej metadanych multimediów. Oferty i odpowiedzi są przekazywane w formacie Session Description Protocol (SDP), który wygląda tak:

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

Chcesz wiedzieć, co to wszystko oznacza? Zapoznaj się z przykładami Internet Engineering Task Force (IETF).

Pamiętaj, że WebRTC został zaprojektowany tak, aby można było dostosować ofertę lub odpowiedź przed ustawieniem jej jako opisu lokalnego lub zdalnego przez edytowanie wartości w tekście SDP. Na przykład funkcja preferAudioCodec() w appr.tc może służyć do ustawiania domyślnego kodeka i szybkości transmisji bitów. SDP jest dość trudny w manipulowaniu za pomocą JavaScriptu. Trwa dyskusja, czy przyszłe wersje WebRTC powinny zamiast tego używać JSON-a, ale trzymanie się SDP ma pewne zalety.

RTCPeerConnection Interfejs API i sygnalizacja: oferta, odpowiedź i kandydat

RTCPeerConnection to interfejs API używany przez aplikacje WebRTC do tworzenia połączeń między urządzeniami i przesyłania dźwięku oraz obrazu.

Aby zainicjować ten proces, RTCPeerConnection musi wykonać 2 zadania:

  • Określ lokalne warunki dotyczące multimediów, takie jak rozdzielczość i możliwości kodeka. Są to metadane używane w mechanizmie oferty i odpowiedzi.
  • Pobierz potencjalne adresy sieciowe hosta aplikacji, zwane kandydatami.

Po ustaleniu tych lokalnych danych należy je wymienić z odległym urządzeniem za pomocą mechanizmu sygnalizacyjnego.

Wyobraź sobie, że Alicja próbuje zadzwonić do Ewy. Oto pełny mechanizm oferty/odpowiedzi ze wszystkimi szczegółami:

  1. Alicja tworzy obiekt RTCPeerConnection.
  2. Alicja tworzy ofertę (opis sesji SDP) za pomocą metody RTCPeerConnection createOffer().
  3. Alicja dzwoni pod numer setLocalDescription() z ofertą.
  4. Alice przekształca ofertę w ciąg znaków i wysyła ją do Ewy za pomocą mechanizmu sygnalizacyjnego.
  5. Ewa dzwoni do setRemoteDescription() z ofertą Alicji, aby RTCPeerConnection dowiedział się o konfiguracji Alicji.
  6. Ewa dzwoni createAnswer(), a wywołanie zwrotne sukcesu przekazuje lokalny opis sesji – odpowiedź Ewy.
  7. Ewa ustawia swoją odpowiedź jako opis lokalny, wywołując funkcję setLocalDescription().
  8. Eve używa mechanizmu sygnalizacyjnego, aby wysłać do Alice odpowiedź w formie ciągu znaków.
  9. Alicja ustawia odpowiedź Ewy jako opis sesji zdalnej za pomocą setRemoteDescription().

Alicja i Ewa muszą też wymieniać informacje o sieci. Określenie „znajdowanie kandydatów” odnosi się do procesu wyszukiwania interfejsów sieciowych i portów za pomocą platformy ICE.

  1. Alicja tworzy obiekt RTCPeerConnection z procedurą obsługi onicecandidate.
  2. Funkcja obsługi jest wywoływana, gdy dostępne staną się kandydaci sieci.
  3. W procedurze obsługi Alice wysyła do Ewy dane kandydata w postaci ciągu znaków za pomocą kanału sygnalizacyjnego.
  4. Gdy Ewa otrzyma od Alicji wiadomość z kandydatem, wywoła funkcję addIceCandidate(), aby dodać kandydata do opisu zdalnego urządzenia.

JSEP obsługuje ICE Candidate Trickling, co pozwala dzwoniącemu na stopniowe przekazywanie kandydatów do dzwonionej osoby po złożeniu początkowej oferty, a dzwonionej osobie na rozpoczęcie działania w ramach połączenia i skonfigurowanie połączenia bez czekania na przybycie wszystkich kandydatów.

Kod WebRTC do sygnalizacji

Poniższy fragment kodu to przykład kodu W3C, który podsumowuje cały proces sygnalizacji. Kod zakłada istnienie mechanizmu sygnalizacyjnego SignalingChannel. Sygnalizację omówimy bardziej szczegółowo w dalszej części tego artykułu.

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

Aby zobaczyć procesy wymiany ofert i odpowiedzi oraz kandydatów w działaniu, odwiedź stronę simpl.info RTCPeerConnection i sprawdź dziennik konsoli w przykładzie czatu wideo na jednej stronie. Jeśli chcesz dowiedzieć się więcej, pobierz pełny zrzut sygnałów i statystyk WebRTC ze strony about://webrtc-internals w Google Chrome lub opera://webrtc-internals w Operze.

Odkrywanie urządzeń równorzędnych

To bardziej wyrafinowany sposób na zadanie pytania „Jak znaleźć osobę, z którą mogę porozmawiać?”.

W przypadku połączeń telefonicznych masz numery telefonów i katalogi. W przypadku czatu wideo i wiadomości online potrzebujesz systemów zarządzania tożsamością i obecnością oraz sposobu inicjowania sesji przez użytkowników. Aplikacje WebRTC potrzebują sposobu, aby klienci mogli sygnalizować sobie nawzajem, że chcą rozpocząć połączenie lub do niego dołączyć.

Mechanizmy wykrywania elementów równorzędnych nie są zdefiniowane przez WebRTC i nie będziemy tu omawiać opcji. Może to być tak proste, jak wysłanie e-maila lub wiadomości z adresem URL. W przypadku aplikacji do czatu wideo, takich jak Talky, tawk.toBrowser Meeting, zapraszasz osoby do rozmowy, udostępniając im niestandardowy link. Deweloper Chris Ball stworzył interesujący eksperyment serverless-webrtc, który umożliwia uczestnikom połączeń WebRTC wymianę metadanych za pomocą dowolnej usługi przesyłania wiadomości, np. komunikatora internetowego, poczty e-mail lub gołębia pocztowego.

Jak utworzyć usługę sygnalizacyjną?

Powtórzmy, że protokoły i mechanizmy sygnalizacyjne nie są zdefiniowane w standardach WebRTC. Niezależnie od wybranej opcji potrzebujesz serwera pośredniczącego do wymiany wiadomości sygnalizacyjnych i danych aplikacji między klientami. Niestety aplikacja internetowa nie może po prostu krzyknąć w internecie: „Połącz mnie z moim znajomym!”.

Na szczęście wiadomości sygnalizacyjne są małe i wymieniane głównie na początku połączenia. Podczas testów z użyciem appr.tc w sesji czatu wideo usługa sygnalizacyjna obsłużyła łącznie około 30–45 wiadomości o łącznym rozmiarze około 10 KB.

Usługi sygnalizacyjne WebRTC nie tylko nie wymagają dużej przepustowości, ale też nie zużywają dużo mocy obliczeniowej ani pamięci, ponieważ muszą jedynie przekazywać wiadomości i przechowywać niewielką ilość danych o stanie sesji, takich jak informacje o tym, którzy klienci są połączeni.

Wysyłanie wiadomości push z serwera do klienta

Usługa przesyłania wiadomości do sygnalizacji musi być dwukierunkowa: klient – serwer i serwer – klient. Komunikacja dwukierunkowa jest sprzeczna z modelem żądanie/odpowiedź klienta/serwera HTTP, ale przez wiele lat opracowywano różne obejścia, takie jak długie odpytywanie, aby przesyłać dane z usługi działającej na serwerze WWW do aplikacji internetowej działającej w przeglądarce.

Ostatnio EventSource API zostało powszechnie wdrożone. Umożliwia to wysyłanie zdarzeń z serwera – danych przesyłanych z serwera internetowego do klienta przeglądarki za pomocą protokołu HTTP. EventSource jest przeznaczony do przesyłania wiadomości w jednym kierunku, ale można go używać w połączeniu z XHR do tworzenia usługi wymiany wiadomości sygnalizacyjnych. Usługa sygnalizacyjna przekazuje wiadomość od dzwoniącego, dostarczoną przez żądanie XHR, przez EventSource do odbiorcy.

WebSocket to bardziej naturalne rozwiązanie przeznaczone do dwukierunkowej komunikacji między klientem a serwerem – wiadomości mogą przepływać w obu kierunkach w tym samym czasie. Zaletą usługi sygnalizacyjnej opartej na czystym protokole WebSocket lub zdarzeniach wysyłanych przez serwer (EventSource) jest to, że backend tych interfejsów API można zaimplementować w różnych platformach internetowych, które są powszechne w większości pakietów hostingowych dla języków takich jak PHP, Python i Ruby.

Wszystkie nowoczesne przeglądarki z wyjątkiem Opery Mini obsługują WebSocket, a co ważniejsze, wszystkie przeglądarki obsługujące WebRTC obsługują też WebSocket zarówno na komputerach, jak i na urządzeniach mobilnych. Wszystkie połączenia powinny korzystać z TLS, aby zapobiec przechwytywaniu nieszyfrowanych wiadomości i ograniczyć problemy z przechodzeniem przez serwery proxy. (Więcej informacji o WebSocket i przechodzeniu przez serwery proxy znajdziesz w rozdziale o WebRTC w książce High Performance Browser Networking Ilyi Grigorika).

Sygnalizację można też obsługiwać, zmuszając klientów WebRTC do wielokrotnego odpytywania serwera wiadomości za pomocą Ajaxa, ale prowadzi to do wielu zbędnych żądań sieciowych, co jest szczególnie problematyczne w przypadku urządzeń mobilnych. Nawet po nawiązaniu sesji węzły muszą sprawdzać, czy nie ma wiadomości sygnalizacyjnych w przypadku zmian lub zakończenia sesji przez inne węzły. Przykładowa aplikacja WebRTC Book korzysta z tej opcji z pewnymi optymalizacjami częstotliwości odpytywania.

Sygnalizacja skali

Chociaż usługa sygnalizacyjna zużywa stosunkowo mało przepustowości i mocy obliczeniowej na klienta, serwery sygnalizacyjne popularnej aplikacji mogą obsługiwać wiele wiadomości z różnych lokalizacji przy wysokim poziomie współbieżności. Aplikacje WebRTC, które generują duży ruch, potrzebują serwerów sygnalizacyjnych, które są w stanie obsłużyć znaczne obciążenie. Nie podajesz tu szczegółów, ale istnieje wiele opcji wysyłania dużej liczby wiadomości o wysokiej skuteczności, w tym:

  • eXtensible Messaging and Presence Protocol (XMPP), pierwotnie znany jako Jabber – protokół opracowany na potrzeby komunikatorów internetowych, który może być używany do sygnalizacji (implementacje serwera obejmują ejabberdOpenfire). Klienci JavaScript, np. Strophe.js, używają BOSH do emulowania strumieniowego przesyłania dwukierunkowego, ale z różnych powodów BOSH może nie być tak wydajny jak WebSocket i z tych samych powodów może nie być dobrze skalowalny). (Przy okazji: Jingle to rozszerzenie XMPP umożliwiające połączenia głosowe i wideo. Projekt WebRTC korzysta z komponentów sieciowych i transportowych z biblioteki libjingle, która jest implementacją Jingle w C++).

  • Biblioteki open source, takie jak ZeroMQ (używana przez TokBox w usłudze Rumour) i OpenMQ (NullMQ stosuje koncepcje ZeroMQ na platformach internetowych przy użyciu protokołu STOMP przez WebSocket).

  • Komercyjne platformy do przesyłania wiadomości w chmurze, które korzystają z WebSocket (choć mogą też używać długiego sondowania), takie jak Pusher, KaazingPubNub (PubNub ma też interfejs API dla WebRTC).

  • komercyjne platformy WebRTC, takie jak vLine;

(Szczegółową listę usług i bibliotek do przesyłania wiadomości znajdziesz w przewodniku po technologiach internetowych w czasie rzeczywistym autorstwa dewelopera Phila Leggettera).

Tworzenie usługi sygnalizacyjnej za pomocą Socket.io w Node

Poniżej znajduje się kod prostej aplikacji internetowej, która korzysta z usługi sygnalizacyjnej utworzonej za pomocą Socket.ioNode. Dzięki swojej konstrukcji Socket.io ułatwia tworzenie usług do wymiany wiadomości.Jest szczególnie przydatny w przypadku sygnalizacji WebRTC ze względu na wbudowaną koncepcję pokoi. Ten przykład nie jest przeznaczony do skalowania jako usługa sygnalizacyjna klasy produkcyjnej, ale jest łatwy do zrozumienia w przypadku stosunkowo małej liczby użytkowników.

Socket.io używa protokołu WebSocket z mechanizmami rezerwowymi: długim odpytywaniem AJAX, strumieniowaniem AJAX w wielu częściach, elementem iframe Forever i odpytywaniem JSONP. Został on przeniesiony na różne platformy backendowe, ale jest najbardziej znany z wersji Node użytej w tym przykładzie.

W tym przykładzie nie ma WebRTC. Ma ona tylko pokazywać, jak wbudować sygnalizację w aplikację internetową. Wyświetl dziennik konsoli, aby zobaczyć, co się dzieje, gdy klienci dołączają do pokoju i wymieniają wiadomości. W tym samouczku WebRTC znajdziesz szczegółowe instrukcje, jak zintegrować tę funkcję z pełną aplikacją do czatu wideo WebRTC.

Klient 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>

Oto plik JavaScript main.js, do którego odwołuje się klient:

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

Oto pełna aplikacja serwera:

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

  });

});

(Nie musisz się tego uczyć. W tym przykładzie jest ona używana przypadkowo).

Aby uruchomić tę aplikację na hoście lokalnym, musisz mieć zainstalowane Node, Socket.IO i node-static. Węzeł można pobrać ze strony Node.js (instalacja jest prosta i szybka). Aby zainstalować Socket.IO i node-static, uruchom Menedżera pakietów Node w terminalu w katalogu aplikacji:

npm install socket.io
npm install node-static

Aby uruchomić serwer, uruchom w terminalu w katalogu aplikacji to polecenie:

node server.js

W przeglądarce otwórz localhost:2013. Otwórz nową kartę lub nowe okno w dowolnej przeglądarce i ponownie otwórz localhost:2013. Aby zobaczyć, co się dzieje, sprawdź konsolę. W Chrome i Operze możesz otworzyć konsolę za pomocą Narzędzi dla deweloperów w Google Chrome, naciskając klawisz Ctrl+Shift+J (lub Command+Option+J na Macu).

Niezależnie od wybranego sposobu sygnalizowania backend i aplikacja kliencka muszą co najmniej udostępniać usługi podobne do tych w tym przykładzie.

Pułapki związane z wysyłaniem sygnałów

  • RTCPeerConnection nie rozpocznie zbierania kandydatów, dopóki nie zostanie wywołana funkcja setLocalDescription(). Jest to wymagane w projekcie JSEP IETF.
  • Korzystaj z funkcji Trickle ICE. Zadzwoń pod numer addIceCandidate(), gdy tylko kandydaci przyjdą.

Gotowe serwery ostrzegające

Jeśli nie chcesz tworzyć własnego serwera, możesz skorzystać z kilku dostępnych serwerów sygnalizacyjnych WebRTC, które używają Socket.IO, tak jak w poprzednim przykładzie, i są zintegrowane z bibliotekami JavaScript klienta WebRTC:

  • webRTC.io to jedna z pierwszych bibliotek abstrakcji dla WebRTC.
  • Signalmaster to serwer sygnalizacyjny utworzony do użytku z biblioteką klienta JavaScript SimpleWebRTC.

Jeśli nie chcesz pisać żadnego kodu, możesz skorzystać z komercyjnych platform WebRTC, które oferują firmy takie jak vLine, OpenTokAsterisk.

Na początku istnienia WebRTC firma Ericsson stworzyła serwer sygnalizacyjny w języku PHP na serwerze Apache. Jest to już nieco przestarzałe, ale jeśli rozważasz coś podobnego, warto przyjrzeć się kodowi.

Bezpieczeństwo sygnalizacji

„Bezpieczeństwo to sztuka sprawiania, by nic się nie działo”.

Salman Rushdie

Szyfrowanie jest obowiązkowe w przypadku wszystkich komponentów WebRTC.

Mechanizmy sygnalizacyjne nie są jednak zdefiniowane przez standardy WebRTC, więc to Ty musisz zadbać o bezpieczeństwo sygnalizacji. Jeśli osoba przeprowadzająca atak przejmie kontrolę nad sygnalizacją, może zatrzymywać sesje, przekierowywać połączenia oraz nagrywać, zmieniać lub wstrzykiwać treści.

Najważniejszym czynnikiem zabezpieczającym sygnalizację jest używanie bezpiecznych protokołów, takich jak HTTPS i WSS (np. TLS), które zapewniają, że wiadomości nie mogą być przechwytywane w postaci niezaszyfrowanej. Uważaj też, aby nie rozsyłać wiadomości sygnalizacyjnych w taki sposób, aby były dostępne dla innych rozmówców korzystających z tego samego serwera sygnalizacyjnego.

Po wysłaniu sygnału: użyj ICE, aby poradzić sobie z NAT-ami i zaporami sieciowymi

W przypadku sygnalizacji metadanych aplikacje WebRTC korzystają z serwera pośredniczącego, ale w przypadku rzeczywistego przesyłania strumieniowego multimediów i danych po nawiązaniu sesji RTCPeerConnection próbuje połączyć klientów bezpośrednio lub w trybie peer-to-peer.

W prostszym świecie każdy punkt końcowy WebRTC miałby unikalny adres, który mógłby wymieniać z innymi urządzeniami, aby komunikować się bezpośrednio.

Proste połączenie peer-to-peer
Świat bez NAT i zapór sieciowych

W rzeczywistości większość urządzeń znajduje się za co najmniej jedną warstwą NAT, niektóre mają oprogramowanie antywirusowe, które blokuje określone porty i protokoły, a wiele z nich znajduje się za serwerami proxy i firmowymi zaporami ogniowymi. Zapora sieciowa i NAT mogą być w rzeczywistości zaimplementowane na tym samym urządzeniu, np. na domowym routerze Wi-Fi.

Węzły równorzędne za NAT-ami i zaporami sieciowymi
Świat rzeczywisty

Aplikacje WebRTC mogą używać struktury ICE, aby pokonać złożoność sieci w rzeczywistym świecie. Aby to umożliwić, aplikacja musi przekazywać adresy URL serwera ICE do RTCPeerConnection zgodnie z opisem w tym artykule.

ICE próbuje znaleźć najlepszą ścieżkę do połączenia urządzeń. Próbuje wszystkich możliwości równolegle i wybiera najbardziej wydajną opcję. ICE najpierw próbuje nawiązać połączenie przy użyciu adresu hosta uzyskanego z systemu operacyjnego urządzenia i karty sieciowej. Jeśli to się nie uda (co się stanie w przypadku urządzeń za NAT), ICE uzyskuje adres zewnętrzny za pomocą serwera STUN, a jeśli to się nie uda, ruch jest kierowany przez serwer przekaźnikowy TURN.

Innymi słowy, serwer STUN służy do uzyskiwania zewnętrznego adresu sieciowego, a serwery TURN służą do przekazywania ruchu, jeśli bezpośrednie połączenie (peer-to-peer) nie działa.

Każdy serwer TURN obsługuje STUN. Serwer TURN to serwer STUN z dodatkową wbudowaną funkcją przekazywania. ICE radzi sobie też ze złożonością konfiguracji NAT. W rzeczywistości przekłuwanie NAT może wymagać więcej niż tylko publicznego adresu IP:port.

Adresy URL serwerów STUN lub TURN są (opcjonalnie) określane przez aplikację WebRTC w obiekcie konfiguracji iceServers, który jest pierwszym argumentem konstruktora RTCPeerConnection. W przypadku appr.tc wartość wygląda tak:

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

Gdy RTCPeerConnection uzyska te informacje, magia ICE zadziała automatycznie. RTCPeerConnection korzysta z platformy ICE, aby wyznaczyć najlepszą ścieżkę między urządzeniami, w razie potrzeby korzystając z serwerów STUN i TURN.

STUN

NAT zapewnia urządzeniu adres IP do użytku w prywatnej sieci lokalnej, ale nie można go używać na zewnątrz. Bez publicznego adresu nie ma możliwości komunikacji między elementami równorzędnymi WebRTC. Aby obejść ten problem, WebRTC używa STUN.

Serwery STUN znajdują się w internecie publicznym i mają jedno proste zadanie – sprawdzanie adresu IP:port przychodzącego żądania (z aplikacji działającej za NAT) i odsyłanie tego adresu w odpowiedzi. Inaczej mówiąc, aplikacja używa serwera STUN, aby wykryć swój adres IP:port z perspektywy publicznej. Ten proces umożliwia elementowi WebRTC uzyskanie publicznie dostępnego adresu, a następnie przekazanie go do innego elementu za pomocą mechanizmu sygnalizacyjnego w celu skonfigurowania bezpośredniego połączenia. (W praktyce różne NAT-y działają na różne sposoby i może być wiele warstw NAT, ale zasada jest taka sama).

Serwery STUN nie muszą wykonywać wielu czynności ani zapamiętywać wielu informacji, więc serwery STUN o stosunkowo niskich parametrach mogą obsługiwać dużą liczbę żądań.

Większość połączeń WebRTC jest nawiązywana za pomocą STUN – według Webrtcstats.com jest to 86%, ale w przypadku połączeń między urządzeniami znajdującymi się za zaporami sieciowymi i złożonymi konfiguracjami NAT może to być mniej.

Połączenie peer-to-peer z użyciem serwera STUN
Używanie serwerów STUN do uzyskiwania publicznych adresów IP:port

TURN

RTCPeerConnection próbuje nawiązać bezpośrednią komunikację między urządzeniami za pomocą protokołu UDP. Jeśli to się nie uda, RTCPeerConnection przełącza się na TCP. Jeśli to się nie uda, serwery TURN mogą być używane jako rezerwa, przekazując dane między punktami końcowymi.

Przypominamy, że serwer TURN służy do przekazywania strumieni audio, wideo i danych między użytkownikami, a nie danych sygnalizacyjnych.

Serwery TURN mają publiczne adresy, więc mogą się z nimi kontaktować inne urządzenia, nawet jeśli znajdują się za zaporami sieciowymi lub serwerami proxy. Serwery TURN mają koncepcyjnie proste zadanie – przekazywanie strumienia. W przeciwieństwie do serwerów STUN zużywają one jednak dużo przepustowości. Innymi słowy, serwery TURN muszą być wydajniejsze.

Połączenie peer-to-peer z użyciem serwera STUN
Pełna wersja: STUN, TURN i sygnalizacja

Ten diagram pokazuje działanie protokołu TURN. Czysty STUN nie zadziałał, więc każdy węzeł korzysta z serwera TURN.

Wdrażanie serwerów STUN i TURN

Na potrzeby testów Google udostępnia publiczny serwer STUN, stun.l.google.com:19302, używany przez appr.tc. W przypadku produkcyjnej usługi STUN/TURN użyj rfc5766-turn-server. Kod źródłowy serwerów STUN i TURN jest dostępny na GitHub. Znajdziesz tam też linki do kilku źródeł informacji o instalacji serwera. Dostępny jest też obraz maszyny wirtualnej dla Amazon Web Services.

Alternatywnym serwerem TURN jest restund, który jest dostępny jako kod źródłowy, a także w AWS. Oto instrukcje, jak skonfigurować restund w Compute Engine.

  1. W razie potrzeby otwórz zaporę sieciową dla tcp=443, udp/tcp=3478.
  2. Utwórz 4 instancje, po jednej dla każdego publicznego adresu IP, z obrazem Standard Ubuntu 12.06.
  3. Skonfiguruj lokalną zaporę sieciową (zezwalaj na połączenia z dowolnego miejsca).
  4. Zainstaluj narzędzia: shell sudo apt-get install make sudo apt-get install gcc
  5. Zainstaluj libre ze strony creytiv.com/re.html.
  6. Pobierz restund z creytiv.com/restund.html i rozpakuj.
  7. wget hancke.name/restund-auth.patch i zastosuj za pomocą polecenia patch -p1 < restund-auth.patch.
  8. Uruchom make, sudo make install dla libre i restund.
  9. Dostosuj restund.conf do swoich potrzeb (zastąp adresy IP i upewnij się, że zawiera ten sam klucz tajny) i skopiuj do /etc.
  10. Skopiuj restund/etc/restund do /etc/init.d/.
  11. Skonfiguruj restund:
    1. Ustaw LD_LIBRARY_PATH.
    2. Skopiuj restund.conf do /etc/restund.conf.
    3. Ustaw restund.conf, aby używać odpowiedniej wartości 10. adres IP,
  12. Uruchamianie restund
  13. Testowanie za pomocą klienta stund na maszynie zdalnej: ./client IP:port

Poza połączeniami 1:1: WebRTC w połączeniach wielostronnych

Możesz też zapoznać się z proponowanym przez Justina Uberti standardem IETF dotyczącym interfejsu API REST do uzyskiwania dostępu do usług TURN.

Łatwo wyobrazić sobie przypadki użycia strumieniowania multimediów, które wykraczają poza proste połączenie jeden na jeden. Może to być np. konferencja wideo z udziałem grupy współpracowników lub wydarzenie publiczne z jednym prelegentem i setkami lub milionami widzów.

Aplikacja WebRTC może używać wielu połączeń RTCPeerConnection, dzięki czemu każdy punkt końcowy łączy się z każdym innym punktem końcowym w konfiguracji siatki. Takie podejście stosują aplikacje, np. talky.io, i sprawdza się ono znakomicie w przypadku niewielkiej liczby osób. Poza tym przetwarzanie i zużycie przepustowości stają się nadmierne, zwłaszcza w przypadku klientów mobilnych.

Mesh: małe połączenie N-osobowe
Topologia pełnej sieci typu mesh: każdy jest połączony z każdym

Aplikacja WebRTC może też wybrać jeden punkt końcowy do dystrybucji strumieni do wszystkich innych w konfiguracji gwiazdy. Możesz też uruchomić punkt końcowy WebRTC na serwerze i skonstruować własny mechanizm redystrybucji (przykładowa aplikacja kliencka jest dostępna na stronie webrtc.org).

Od wersji 31 Chrome i 18 Opery MediaStream z jednego RTCPeerConnection może być używany jako dane wejściowe dla innego. Może to umożliwić bardziej elastyczne architektury, ponieważ aplikacja internetowa może obsługiwać routing połączeń, wybierając, z którym innym użytkownikiem ma się połączyć. Aby zobaczyć to w działaniu, zapoznaj się z przykładami WebRTC dotyczącymi przekazywania połączeń równorzędnych i przykładami WebRTC dotyczącymi wielu połączeń równorzędnych.

Multipoint Control Unit

Lepszym rozwiązaniem w przypadku dużej liczby punktów końcowych jest użycie jednostki sterującej wieloma punktami (MCU). Jest to serwer, który działa jako pomost do dystrybucji multimediów między dużą liczbą uczestników. Jednostki MCU mogą obsługiwać różne rozdzielczości, kodeki i liczbę klatek na sekundę podczas wideokonferencji, a także transkodować, selektywnie przekazywać strumienie oraz miksować i nagrywać dźwięk i obraz. W przypadku połączeń grupowych należy wziąć pod uwagę kilka kwestii, w szczególności sposób wyświetlania wielu sygnałów wideo i miksowania dźwięku z wielu źródeł. Platformy chmurowe, takie jak vLine, również próbują optymalizować routing ruchu.

Możesz kupić kompletny pakiet sprzętowy MCU lub samodzielnie go skonfigurować.

Widok z tyłu urządzenia Cisco MCU5300
Tył jednostki obliczeniowej Memcache Cisco

Dostępnych jest kilka opcji oprogramowania open source dla mikrokontrolerów. Na przykład Licode (wcześniej znana jako Lynckia) tworzy otwarty MCU dla WebRTC. OpenTok ma Mantis.

Więcej niż przeglądarki: VoIP, telefony i wiadomości

Standardowy charakter WebRTC umożliwia nawiązanie komunikacji między aplikacją WebRTC działającą w przeglądarce a urządzeniem lub platformą działającą na innej platformie komunikacyjnej, takiej jak telefon lub system wideokonferencyjny.

SIP to protokół sygnalizacyjny używany przez systemy VoIP i wideokonferencyjne. Aby umożliwić komunikację między aplikacją internetową WebRTC a klientem SIP, takim jak system wideokonferencyjny, WebRTC potrzebuje serwera proxy do pośredniczenia w sygnalizacji. Sygnalizacja musi przechodzić przez bramę, ale po nawiązaniu komunikacji ruch SRTP (wideo i audio) może przepływać bezpośrednio między urządzeniami.

Publiczna komutowana sieć telefoniczna (PSTN) to sieć komutowana wszystkich „zwykłych” telefonów analogowych. W przypadku połączeń między aplikacjami internetowymi WebRTC a telefonami ruch musi przechodzić przez bramę PSTN. Podobnie aplikacje internetowe WebRTC potrzebują pośredniczącego serwera XMPP do komunikacji z punktami końcowymi Jingle, takimi jak komunikatory internetowe. Jingle zostało opracowane przez Google jako rozszerzenie protokołu XMPP, które umożliwia korzystanie z głosu i wideo w usługach przesyłania wiadomości. Obecne implementacje WebRTC są oparte na bibliotece C++ libjingle, czyli implementacji Jingle opracowanej początkowo na potrzeby Talk.

Wiele aplikacji, bibliotek i platform korzysta z możliwości WebRTC do komunikowania się ze światem zewnętrznym:

  • sipML5: klient SIP w JavaScript o otwartym kodzie źródłowym.
  • jsSIP: biblioteka JavaScript SIP
  • Phono: interfejs API JavaScript do obsługi telefonów typu open source, który jest wtyczką.
  • Zingaya: widżet telefonu do umieszczenia na stronie
  • Twilio połączenia głosowe i wiadomości
  • Uberconference konferencje

Deweloperzy sipML5 stworzyli też bramę webrtc2sip. Firmy Tethr i Tropo opracowały platformę komunikacji w przypadku katastrof „w walizce” z komórką OpenBTS, która umożliwia komunikację między telefonami z podstawową przeglądarką a komputerami za pomocą WebRTC. To komunikacja telefoniczna bez operatora.

Więcej informacji

Codelab WebRTC zawiera szczegółowe instrukcje tworzenia aplikacji do czatu wideo i tekstowego za pomocą usługi sygnalizacyjnej Socket.io działającej w Node.

Prezentacja WebRTC na Google I/O w 2013 r. z udziałem kierownika technicznego WebRTC, Justina Ubertiego

Prezentacja Chrisa Wilsona na SFHTML5 – Wprowadzenie do aplikacji WebRTC

350-stronicowa książka WebRTC: APIs and RTCWEB Protocols of the HTML5 Real-Time Web zawiera wiele szczegółowych informacji o ścieżkach danych i sygnalizacji oraz wiele szczegółowych diagramów topologii sieci.

WebRTC and Signaling: What Two Years Has Taught Us (WebRTC i sygnalizacja: czego nauczyliśmy się przez 2 lata) – post na blogu TokBox wyjaśniający, dlaczego pominięcie sygnalizacji w specyfikacji było dobrym pomysłem.

Ben Strongpraktycznym przewodniku po tworzeniu aplikacji WebRTC podaje wiele informacji o topologiach i infrastrukturze WebRTC.

Rozdział o WebRTC w książce Ilyi Grigorika High Performance Browser Networking szczegółowo omawia architekturę, przypadki użycia i wydajność WebRTC.