Tworzenie usług backendu niezbędnych dla aplikacji WebRTC

Co to jest sygnalizacja?

Sygnalizowanie to proces koordynowania komunikacji. Aby aplikacja WebRTC mogła skonfigurować połączenie, jej klienci muszą wymienić się tymi informacjami:

  • komunikaty kontrolne sesji służące do otwierania i zamykania komunikacji;
  • Komunikaty o błędach
  • metadane multimediów, takie jak kodeki, ustawienia kodeków, przepustowość i typy multimediów;
  • Dane klucza używane do nawiązywania bezpiecznych połączeń
  • dane sieciowe, takie jak adres IP i port hosta widoczne z zewnątrz;

Ten proces sygnalizacji wymaga, aby klienci mogli przesyłać wiadomości w obie strony. Interfejsy WebRTC nie obsługują tego mechanizmu. Musisz go zbudować samodzielnie. W dalszej części tego artykułu dowiesz się, jak tworzyć usługę sygnalizacji. Najpierw jednak musisz poznać kontekst.

Dlaczego sygnalizacja nie jest zdefiniowana przez WebRTC?

Aby uniknąć nadmiarowości i zmaksymalizować zgodność z istniejącymi technologiami, metody i protokoły sygnalizacji nie są określone w standardach WebRTC. Takie podejście jest opisane w protokole JavaScript Session Establishment Protocol (JSEP):

Architektura JSEP pozwala też uniknąć konieczności zapisywania stanu przez przeglądarkę, czyli działania jako sygnalizator stanu. Byłoby to problematyczne, gdyby na przykład dane sygnalizacji były tracone za każdym razem, gdy strona była ponownie wczytywana. Zamiast tego stan sygnalizacji może być zapisywany na serwerze.

Schemat architektury JSEP
Architektura JSEP

JSEP wymaga wymiany między urządzeniami oferty i odpowiedzi, czyli wspomnianych powyżej metadanych multimediów. Oferty i odpowiedzi są przesyłane w formacie SDP (Session Description Protocol), 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 tak naprawdę oznaczają te wszystkie dziwne terminy związane z SDP? Zapoznaj się z przykładami IETF (Internet Engineering Task Force).

Pamiętaj, że WebRTC jest tak zaprojektowany, że ofertę lub odpowiedź można dostosować przed ustawieniem jako opis lokalny lub zdalny, edytując wartości w tekście SDP. Na przykład do ustawienia domyślnego kodeka i szybkości transmisji bitów można użyć funkcji preferAudioCodec() w pliku appr.tc. SDP jest dość trudny do manipulowania za pomocą JavaScriptu i toczy się dyskusja, czy przyszłe wersje WebRTC powinny używać zamiast tego JSON, ale SDP ma pewne zalety.

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

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

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

  • Sprawdź lokalne warunki dotyczące multimediów, takie jak rozdzielczość i możliwości kodeków. To metadane używane w mechanizmie oferty i odpowiedzi.
  • Uzyskać potencjalne adresy sieciowe hosta aplikacji, czyli kandydatów.

Po ustaleniu tych danych lokalnych należy je wymienić z urządzeniem zdalnym za pomocą mechanizmu sygnalizacji.

Wyobraź sobie, że Alicja próbuje zadzwonić do Ewy. Oto pełny mechanizm oferty i odpowiedzi:

  1. Alice tworzy obiekt RTCPeerConnection.
  2. Alicja tworzy ofertę (opis sesji SDP) za pomocą metody RTCPeerConnection createOffer().
  3. Alice dzwoni do setLocalDescription() z ofertą.
  4. Alice tworzy ofertę w postaci ciągu znaków i wysyła ją do Eve za pomocą mechanizmu sygnalizacji.
  5. Eve dzwoni do setRemoteDescription() z ofertą Alice, aby jej RTCPeerConnection powiadomić o jej konfiguracji.
  6. Eve wywołuje funkcję createAnswer(), a zwracana przez nią funkcja otrzymuje opis lokalnej sesji – odpowiedź Eve.
  7. Eve ustawia odpowiedź jako lokalny opis, wywołując setLocalDescription().
  8. Następnie Eve używa mechanizmu sygnalizacji, aby wysłać Alice odpowiedź w postaci ciągu znaków.
  9. Alice ustawia odpowiedź Eve jako opis sesji zdalnej za pomocą setRemoteDescription().

Alice i Eve muszą też wymienić się informacjami o sieci. Wyrażenie „znajdowanie kandydatów” odnosi się do procesu znajdowania interfejsów sieciowych i portów za pomocą ramy ICE.

  1. Alice tworzy obiekt RTCPeerConnection z obsługą onicecandidate.
  2. Obsługa jest wywoływana, gdy dostępne są kandydaci sieci.
  3. W obiekcie obsługi Alice wysyła do Eve dane kandydatów w postaci ciągu znaków przez kanał sygnalizacji.
  4. Gdy Ewa otrzyma od Alicji wiadomość od kandydata, wywołuje funkcję addIceCandidate(), aby dodać kandydata do opisu zdalnego peera.

JSEP obsługuje podawanie kandydatów ICE w sposób ciągły, co pozwala dzwoniącemu stopniowo przekazywać kandydatów do wywoływanego urządzenia po początkowej ofercie, a temu ostatniemu rozpoczynać działanie w ramach połączenia i nawiązywać połączenie bez oczekiwania na wszystkie 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 pewnego mechanizmu sygnalizacji, SignalingChannel. Temat sygnalizacji omówimy bardziej szczegółowo w następnym module.

// 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 oferty/odpowiedzi i wymiany kandydatów w działaniu, otwórz simpl.info RTCPeerConnection i sprawdź dziennik konsoli, aby zobaczyć przykład czatu wideo na jednej stronie. Jeśli chcesz uzyskać więcej informacji, pobierz pełny zrzut danych dotyczących sygnalizacji i statystyk WebRTC ze strony about:/webrtc-internals w Google Chrome lub ze strony opera:/webrtc-internals w Opera.

Odkrywanie aplikacji z grupy porównawczej

To wyrafinowany sposób na zapytanie „Jak znaleźć kogoś, z kim można porozmawiać?”.

W przypadku połączeń telefonicznych masz numery telefonów i książki telefoniczne. Aby prowadzić czat wideo i wysyłać wiadomości, potrzebujesz systemów zarządzania tożsamością i obecnością oraz sposobu na inicjowanie sesji przez użytkowników. Aplikacje WebRTC potrzebują sposobu na to, aby klienci mogli sygnalizować sobie nawzajem, że chcą rozpocząć lub dołączyć do rozmowy.

Mechanizmy wykrywania urządzeń nie są zdefiniowane przez WebRTC i nie ma tu żadnych opcji. Wystarczy wysłać e-maila lub wiadomość z adresem URL. W przypadku aplikacji do czatu wideo, takich jak Talky, tawk.to i Browser Meeting, możesz zapraszać osoby do rozmowy, udostępniając niestandardowy link. Programista Chris Ball przeprowadził interesujący eksperyment z użyciem WebRTC bez serwera, który umożliwia uczestnikom połączeń WebRTC wymianę metadanych za pomocą dowolnej usługi przesyłania wiadomości, takiej jak komunikator, e-mail czy gołąbek pocztowy.

Jak utworzyć usługę sygnalizacji?

Powtarzam, że protokoły i mechanizmy sygnalizacji nie są zdefiniowane przez standardy WebRTC. W obu przypadkach potrzebujesz serwera pośredniczącego, który będzie wymieniać się z klientami wiadomościami sygnalizacyjnymi i danymi aplikacji. Niestety aplikacja internetowa nie może po prostu krzyczeć do internetu „Połącz mnie z moją znajomą osobą”.

Na szczęście sygnały są małe i przesyłane głównie na początku połączenia. Podczas testowania appr.tc w ramach sesji czatu wideo usługa sygnalizacji obsługiwała łącznie około 30–45 wiadomości o łącznym rozmiarze około 10 KB.

Usługi sygnalizacji WebRTC są nie tylko stosunkowo niewymagające pod względem przepustowości, ale też nie zużywają dużo zasobów procesora ani pamięci, ponieważ muszą tylko 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 z serwera do klienta;

Usługa przesyłania sygnałów musi być dwukierunkowa: od klienta do serwera i od serwera do klienta. Komunikacja dwukierunkowa jest niezgodna z modelem żądania/odpowiedzi klienta i serwera HTTP, ale przez wiele lat opracowano różne rozwiązania, takie jak długie sondowanie, aby przesyłać dane z usługi działającej na serwerze WWW do aplikacji internetowej w przeglądarce.

Niedawno interfejs API EventSource został szerzej wdrożony. Umożliwia to zdarzenia wysyłane przez serwer – dane wysyłane z serwera internetowego do klienta przeglądarki przez HTTP. EventSource jest przeznaczony do obsługi komunikacji jednokierunkowej, ale można go używać w połączeniu z XHR do tworzenia usługi wymiany sygnałów. Usługa sygnalizacji przekazuje wiadomość od rozmówcy, dostarczoną przez żądanie XHR, przesyłając ją przez EventSource do rozmówcy.

WebSocket to bardziej naturalne rozwiązanie, które umożliwia pełną dwukierunkową komunikację klienta z serwerem – wiadomości mogą przepływać w obu kierunkach w tym samym czasie. Jedną z zalet usługi sygnalizacji opartej na czystych zdarzeniach WebSocket lub zdarzeniach wysyłanych przez serwer (EventSource) jest to, że backend dla tych interfejsów API można zaimplementować w różnych ramach internetowych, które są wspólne dla większości pakietów hostingu internetowego dla języków takich jak PHP, Python i Ruby.

Wszystkie nowoczesne przeglądarki oprócz Opery Mini obsługują WebSocket, a co ważniejsze, wszystkie przeglądarki, które obsługują WebRTC, będą obsługiwać też WebSocket, zarówno na komputerach, jak i na urządzeniach mobilnych. TLS powinien być używany w przypadku wszystkich połączeń, aby zapewnić, że wiadomości nie będą przechwytywane w postaci niezaszyfrowanej, a także zmniejszyć problemy z przekierowywaniem przez serwer proxy. (więcej informacji o WebSocket i przechodzeniu przez serwer proxy znajdziesz w rozdziale WebRTC w książce Ilya Grigorik High Performance Browser Networking).

Obsługę sygnalizacji można też realizować, prosząc klientów WebRTC o powtarzające się wysyłanie zapytań do serwera wiadomości za pomocą Ajaxa, ale powoduje to wiele zbędnych zapytań sieciowych, co jest szczególnie problematyczne w przypadku urządzeń mobilnych. Nawet po nawiązaniu sesji inne komputery muszą sprawdzać, czy są jakieś sygnalizacyjne wiadomości, na wypadek zmian lub zakończenia sesji przez inne komputery. Przykład aplikacji WebRTC Book korzysta z tej opcji z pewnymi optymalizacjami częstotliwości odpytywania.

Sygnalizacja na dużą skalę

Mimo że usługa sygnalizacji zużywa stosunkowo niewiele przepustowości i mocy procesora na klienta, serwery sygnalizacji popularnej aplikacji mogą obsługiwać dużą liczbę wiadomości z różnych lokalizacji z wysoką współbieżnością. Aplikacje WebRTC, które generują dużo ruchu, potrzebują serwerów sygnalizacji, które są w stanie obsłużyć znaczne obciążenie. Nie musisz tu podawać szczegółów, ale jest kilka opcji wysyłania dużej liczby skutecznych wiadomości, takich jak:

  • eXtensible Messaging and Presence Protocol (XMPP), pierwotnie znany jako Jabber – protokół opracowany na potrzeby komunikatora, który może być używany do sygnalizacji (implementacje serwera to ejabberd i Openfire. Klienci JavaScript, tacy jak 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 skalować się dobrze. (Jingle to rozszerzenie XMPP umożliwiające prowadzenie rozmów głosowych i wideo. Projekt WebRTC używa komponentów sieci i transportu z biblioteki libjingle (implementacji Jingle w C++).

  • Biblioteki open source, takie jak ZeroMQ (używana przez TokBox w usłudze Rumour) i OpenMQ (NullMQ stosuje koncepcje ZeroMQ w przypadku platform internetowych korzystających z protokołu STOMP w usłudze WebSocket).

  • Komercyjne platformy do przesyłania wiadomości w chmurze, które korzystają z WebSocket (chociaż mogą stosować długie zapytania pollingowe), takie jak Pusher, Kaazing i PubNub (PubNub ma też interfejs API dla WebRTC).

  • komercyjne platformy WebRTC, takie jak vLine

(Przewodnik po technologiach internetowych w czasie rzeczywistym autorstwa Phila Leggettera zawiera obszerną listę usług i bibliotek do obsługi wiadomości).

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

Poniżej znajduje się kod prostej aplikacji internetowej, która korzysta z usługi sygnalizacji utworzonej za pomocą Socket.ioNode. Dzięki projektowi Socket.io można łatwo tworzyć usługi do wymiany wiadomości. Socket.io jest szczególnie odpowiedni do sygnalizacji WebRTC ze względu na wbudowaną koncepcję pokoi. Ten przykład nie jest przeznaczony do skalowania jako usługa sygnalizacji w wersji produkcyjnej, ale jest prosty do zrozumienia dla stosunkowo małej liczby użytkowników.

Socket.io używa protokołu WebSocket z opcjami zapasowymi: długotrwałe sondowanie AJAX, strumieniowe przesyłanie AJAX w wielu częściach, element iframe Forever i sondowanie JSONP. Został on przeniesiony na różne systemy backendowe, ale jest prawdopodobnie najbardziej znany z wersji Node, która jest używana w tym przykładzie.

W tym przykładzie nie ma WebRTC. Służy on tylko do pokazania, jak tworzyć sygnalizację w aplikacji internetowej. W dzienniku konsoli możesz sprawdzić, co się dzieje, gdy klienci dołączają do pokoju i wymieniają wiadomości. W tym laboratorium kodu WebRTC znajdziesz szczegółowe instrukcje integracji z kompletną aplikacją do czatu wideo WebRTC.

Oto 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, na który 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ę tu uczyć o node-static. W tym przykładzie jest ona używana tylko do celów poglądowych.)

Aby uruchomić tę aplikację na localhost, musisz mieć zainstalowane Node, Socket.IO i node-static. Node można pobrać z witryny 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

Otwórz localhost:2013 w przeglądarce. Otwórz nową kartę lub nowe okno w dowolnej przeglądarce i ponownie otwórz localhost:2013. Aby sprawdzić, co się dzieje, otwórz konsolę. W Chrome i Operze możesz otworzyć konsolę za pomocą Ctrl+Shift+J (lub Command+Option+J na Macu).

Niezależnie od wybranego podejścia do sygnalizacji, backend i aplikacja kliencka muszą co najmniej zapewniać usługi podobne do tych w tym przykładzie.

Ważne informacje o sygnalizacji

  • RTCPeerConnection nie zacznie zbierać kandydatów, dopóki nie zostanie wywołana funkcja setLocalDescription(). Wymaga tego projekt JSEP IETF.
  • Korzystanie z usługi Trickle ICE Zadzwoń pod numer addIceCandidate(), gdy tylko kandydaci się pojawią.

gotowe serwery ostrzegające,

Jeśli nie chcesz uruchamiać własnego serwera, możesz skorzystać z kilku serwerów sygnalizacji WebRTC, które używają Socket.IO (podobnie jak w poprzednim przykładzie) i są zintegrowane z bibliotekami JavaScript dla klienta WebRTC:

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

Jeśli nie chcesz pisać kodu, możesz skorzystać z komercyjnych platform WebRTC takich jak vLine, OpenTok czy Asterisk.

Na początku ery WebRTC firma Ericsson stworzyła serwer sygnalizacji za pomocą PHP na serwerze Apache. Jest to obecnie nieco przestarzałe, ale jeśli rozważasz zastosowanie podobnego rozwiązania, warto przyjrzeć się temu kodowi.

Bezpieczeństwo sygnalizacji

„Bezpieczeństwo to sztuka polegająca na tym, aby nic się nie stało”.

Salman Rushdie

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

Mechanizmy sygnalizacji nie są jednak zdefiniowane w standardach WebRTC, więc to od Ciebie zależy, czy zapewnisz bezpieczeństwo sygnalizacji. Jeśli atakującemu uda się przejąć sygnalizację, może on zatrzymać sesje, przekierować połączenia oraz nagrywać, modyfikować lub wstrzykiwać treści.

Najważniejszym czynnikiem w zabezpieczaniu sygnalizacji jest używanie bezpiecznych protokołów – HTTPS i WSS (np. TLS), które zapewniają, że wiadomości nie mogą być przechwytywane w niezaszyfrowanej postaci. Uważaj, aby nie transmitować sygnałów w sposób umożliwiający dostęp innym rozmówcom korzystającym z tego samego serwera sygnałów.

Po sygnalizacji: korzystanie z ICE do radzenia sobie z NAT-ami i zaporami sieciowymi

Do sygnalizacji metadanych aplikacje WebRTC używają serwera pośredniczącego, ale do przesyłania danych i multimediów 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 punktami końcowymi, aby komunikować się bezpośrednio.

proste połączenie typu peer-to-peer,
Świat bez NAT i zapory sieciowej

W rzeczywistości większość urządzeń znajduje się za co najmniej jedną warstwą NAT, niektóre mają program antywirusowy, który blokuje niektóre porty i protokoły, a wiele z nich jest chronionych przez serwery proxy i zapory korporacyjne. Zapora sieciowa i NAT mogą być implementowane przez to samo urządzenie, np. domowy router Wi-Fi.

Peers za NAT-ami i zaporami sieciowymi
Świat rzeczywisty

Aplikacje WebRTC mogą korzystać z ramy ICE, aby uprościć złożoność sieci w rzeczywistym świecie. Aby to umożliwić, aplikacja musi przekazać adresy URL serwerów ICE do usługi RTCPeerConnection, zgodnie z opisem w tym artykule.

ICE próbuje znaleźć najlepszą ścieżkę do połączenia peerów. Wypróbowuje wszystkie 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 się nie powiedzie (co zdarza się w przypadku urządzeń za NAT-em), ICE uzyska zewnętrzny adres za pomocą serwera STUN, a jeśli i to się nie uda, ruch zostanie przekierowany przez serwer przekaźnika TURN.

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

Każdy serwer TURN obsługuje STUN. Serwer TURN to serwer STUN z dodatkową wbudowaną funkcją przekierowania. ICE radzi sobie też z zawiłościami konfiguracji NAT. W rzeczywistości przebicie NAT może wymagać więcej niż tylko publicznego adresu IP:portu.

Adresy URL serwerów STUN lub TURN są (opcjonalnie) podawane przez aplikację WebRTC w obiekcie konfiguracji iceServers, który jest pierwszym argumentem konstruktora RTCPeerConnection. W przypadku parametru appr.tc ta 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 będzie mieć te informacje, magia ICE zadziała automatycznie. RTCPeerConnection korzysta z ramy ICE, aby znaleźć najlepszą ścieżkę między peerami, korzystając w razie potrzeby z serwerów STUN i TURN.

STUN

NAT-y zapewniają urządzeniu adres IP do użytku w ramach prywatnej sieci lokalnej, ale nie można go używać na zewnątrz. Bez adresu publicznego nie ma możliwości komunikacji między peerami WebRTC. Aby rozwiązać ten problem, WebRTC używa protokołu STUN.

Serwery STUN działają w publicznej sieci internetowej i mają jedno proste zadanie – sprawdzają adres IP:port przychodzącego żądania (z aplikacji działającej za NAT) i przesyłają ten adres jako odpowiedź. Inaczej mówiąc, aplikacja używa serwera STUN do wykrywania swojego adresu IP:portu z publicznego punktu widzenia. Ten proces umożliwia urządzeniu WebRTC uzyskanie adresu publicznie dostępnego dla siebie, a następnie przekazanie go innemu urządzeniu za pomocą mechanizmu sygnalizacji w celu skonfigurowania połączenia bezpośredniego. (w praktyce różne typy NAT działają na różne sposoby i może być wiele warstw NAT, ale zasada jest taka sama).

Serwery STUN nie muszą wykonywać zbyt wielu operacji ani zapamiętywać zbyt wielu danych, więc serwery STUN o stosunkowo niskich specyfikacjach mogą obsługiwać dużą liczbę żądań.

Większość połączeń WebRTC nawiązuje połączenie za pomocą STUN (86% według Webrtcstats.com), ale w przypadku połączeń między peerami za zaporami sieciowymi i z kompleksowymi konfiguracjami NAT może być to mniej.

Połączenie typu peer-to-peer z użyciem serwera STUN
Uzyskiwanie publicznych adresów IP:portów za pomocą serwerów STUN

TURN

RTCPeerConnection próbuje skonfigurować bezpośrednią komunikację między punktami końcowymi przez UDP. Jeśli to się nie uda, RTCPeerConnection użyje protokołu TCP. Jeśli to się nie uda, jako rozwiązanie zastępcze można użyć serwerów TURN, które przekazują dane między punktami końcowymi.

Przypominamy, że protokół TURN służy do przekazywania dźwięku, wideo i danych strumieniowych między peerami, a nie do danych sygnalizacyjnych.

Serwery TURN mają adresy publiczne, więc mogą być dostępne dla innych urządzeń, nawet jeśli te znajdują się za zaporami sieciowymi lub serwerami proxy. Serwery TURN mają teoretycznie proste zadanie – przekierowywanie strumienia. W przeciwieństwie do serwerów STUN mają one jednak w budowanej charakterystyce duży pobór przepustowości. Innymi słowy, serwery TURN muszą być bardziej wydajne.

Połączenie typu peer-to-peer z użyciem serwera STUN
The full Monty: STUN, TURN i sygnalizacja

Ten diagram pokazuje działanie funkcji TURN. Czysta usługa STUN nie zadziałała, więc każdy peer korzysta z serwera TURN.

Wdrażanie serwerów STUN i TURN

Na potrzeby testowania Google udostępnia publiczny serwer STUN (stun.l.google.com:19302), który jest 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, gdzie znajdziesz 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, dostępny jako kod źródłowy, a także w AWS. Oto instrukcje konfigurowania zwrotu środków w Compute Engine.

  1. Otwórz zaporę sieciową w razie potrzeby dla tcp=443, udp/tcp=3478.
  2. Utwórz 4 instancji, po jednej dla każdego publicznego adresu IP, z obrazu Standard Ubuntu 12.06.
  3. Skonfiguruj lokalną konfigurację zapory sieciowej (zezwalaj na dowolne połączenia z dowolnego źródła).
  4. Instalowanie narzędzi:shell sudo apt-get install make sudo apt-get install gcc
  5. Zainstaluj Libre z creytiv.com/re.html.
  6. Pobierz restund z creytiv.com/restund.html i rozpakuj.
  7. wget hancke.name/restund-auth.patch i zastosuj patch -p1 < restund-auth.patch.
  8. Uruchom make, sudo make install dla libre i zwrotów.
  9. Dostosuj plik restund.conf do swoich potrzeb (zastąp adresy IP i upewnij się, że zawiera on ten sam udostępniony klucz tajny) i skopiuj go do pliku /etc.
  10. Skopiuj restund/etc/restund do /etc/init.d/.
  11. Konfigurowanie zwrotu środków:
    1. Ustaw LD_LIBRARY_PATH.
    2. Skopiuj restund.conf do /etc/restund.conf.
    3. Ustaw restund.conf, aby użyć prawidłowej wartości 10. adres IP,
  12. Uruchamianie odpowiedzi
  13. Testowanie za pomocą klienta stund na zdalnym komputerze: ./client IP:port

Więcej niż jeden-do-jednego: WebRTC dla wielu osób

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

Łatwo wyobrazić sobie przypadki użycia strumieniowego przesyłania multimediów, które wykraczają poza proste rozmowy jeden na jednego. Może to być na przykład konferencja wideo z udziałem grupy współpracowników lub publiczne wydarzenie z jednym mówcą i setkami lub milionami widzów.

Aplikacja WebRTC może używać wielu połączeń RTCPeerConnections, aby każdy punkt końcowy łączył się z każdym innym punktem końcowym w konfiguracji siatki. Takie podejście stosuje się w przypadku aplikacji takich jak talky.io i działa ono bardzo dobrze w przypadku niewielkiej liczby urządzeń. W przeciwnym razie przetwarzanie i zużycie przepustowości staje się nadmierne, zwłaszcza w przypadku klientów mobilnych.

Mesh: małe połączenie N-stronne
Pełna topologia sieci typu mesh: każdy jest połączony z każdym

Aplikacja WebRTC może też wybrać jeden punkt końcowy, aby rozprowadzać strumienie do wszystkich innych w ramach konfiguracji gwiazdy. Możliwe jest też uruchomienie punktu końcowego WebRTC na serwerze i utworzenie własnego mechanizmu redystrybucji (na stronie webrtc.org znajdziesz przykładową aplikację kliencką).

Od wersji 31 Chrome i Opera 18 MediaStream z jednego RTCPeerConnection może być używany jako dane wejściowe dla innego. Może to umożliwić bardziej elastyczne architektury, ponieważ pozwala aplikacji internetowej na obsługę przekierowywania połączeń przez wybór innego peera. Aby zobaczyć to w działaniu, zapoznaj się z artykułem Przykłady kodu WebRTC: przekazywanie połączenia peer-to-peerPrzykłady kodu WebRTC: wiele połączeń peer-to-peer.

Moduł sterujący wielopunktowy

W przypadku dużej liczby punktów końcowych lepszym rozwiązaniem jest użycie jednostki sterującej wielopunktowej (MCU). Jest to serwer, który działa jako łącznik do dystrybucji multimediów między dużą liczbą uczestników. MCU mogą obsługiwać różne rozdzielczości, kodeki i liczby klatek podczas konferencji wideo, przetwarzać pliki, przekierowywać strumienie selektywnie oraz miksować lub nagrywać dźwięk i obraz. W przypadku połączeń z udziałem wielu osób należy wziąć pod uwagę kilka kwestii, w tym sposób wyświetlania wielu wejść wideo i miksowania dźwięku z wielu źródeł. Platformy chmurowe, takie jak vLine, również próbują optymalizować kierowanie ruchu.

Możesz kupić kompletny pakiet sprzętowy MCU lub stworzyć własny.

Widok z tyłu urządzenia Cisco MCU5300
Tył urządzenia Cisco MCU

Dostępnych jest kilka opcji oprogramowania MCU typu open source. Na przykład Licode (wcześniej Lynckia) udostępnia MCU typu open source dla WebRTC. OpenTok ma Mantis.

Inne niż przeglądarki: VoIP, telefony i wiadomości

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

SIP to protokół sygnalizacji używany przez systemy VoIP i wideokonferencji. Aby umożliwić komunikację między aplikacją internetową WebRTC a klientem SIP, np. systemem wideokonferencyjnym, WebRTC potrzebuje serwera proxy, który będzie pośredniczyć w sygnalizacji. Sygnalizacja musi przechodzić przez bramkę, ale po nawiązaniu komunikacji ruch SRTP (wideo i dźwięk) może być przesyłany bezpośrednio między urządzeniami.

Publiczna komutowana sieć telefoniczna (PSTN) to sieć komutacyjna, która obejmuje wszystkie „zwykłe” analogowe telefony. W przypadku połączeń między aplikacjami internetowymi WebRTC a telefonami ruch musi przechodzić przez bramę PSTN. Podobnie aplikacje internetowe WebRTC potrzebują pośredniego serwera XMPP, aby komunikować się z punktami końcowymi Jingle, takimi jak klienci czatów. Jingle zostało opracowane przez Google jako rozszerzenie XMPP, aby umożliwić przesyłanie głosu i obrazu w usługach przesyłania wiadomości. Obecne implementacje WebRTC są oparte na bibliotece C++ libjingle, która jest implementacją Jingle opracowaną pierwotnie dla Talk.

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

  • sipML5 klient SIP oparty na JavaScript, którego kod jest udostępniony na licencji open source.
  • jsSIP biblioteka SIP w JavaScript.
  • Phono: interfejs API telefonu w JavaScript o otwartym kodzie źródłowym utworzony jako wtyczka.
  • Zingaya widżet telefonu do wklejenia
  • Twilio: głos i wiadomości
  • Uberconference: konferencje.

Deweloperzy sipML5 stworzyli też bramę webrtc2sip. Tethr i Tropo zaprezentowały ramy do komunikacji w przypadku katastrof „w teczce” za pomocą komórki OpenBTS, aby umożliwić komunikację między telefonami z podstawową przeglądarką a komputerami za pomocą WebRTC. To komunikacja telefoniczna bez operatora.

Więcej informacji

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

Prezentacja WebRTC z Google I/O z 2013 r. z Justinem Ubertim, liderem zespołu technicznego WebRTC

Prezentacja Chrisa Wilsona na konferencji SFHTML5 – Wprowadzenie do aplikacji WebRTC

350-stronicowa książka WebRTC: APIs and RTCWEB Protocols of the HTML5 Real-Time Web (WebRTC: interfejsy API i protokoły RTCWEB w czasie rzeczywistym w HTML5) zawiera wiele szczegółowych informacji o danych i sygnalizacji oraz liczne szczegółowe diagramy topologii sieci.

WebRTC i sygnalizacja: co nauczyliśmy się przez 2 lata – post na blogu TokBox o tym, dlaczego wykluczenie sygnalizacji z specyfikacji było dobrym pomysłem

Praktyczny przewodnik po tworzeniu aplikacji WebRTC autorstwa Bena Stronga zawiera wiele informacji o topologiach i infrastrukturze WebRTC.

Rozdział o WebRTC w książce Ilya Grigorik High Performance Browser Networking (Wysoko wydajna komunikacja w przeglądarce) szczegółowo omawia architekturę, przypadki użycia i wydajność WebRTC.