Signaling Server — to serwerowy komponent infrastruktury WebRTC, zapewniający wymianę metadanych między peerami w celu nawiązania i zakończenia połączenia. W przeciwieństwie do ruchu multimedialnego, sygnalizacja może być przesyłana za pomocą dowolnego protokołu — WebSocket, HTTP, XMPP lub SIP. Według MDN Web Docs, 2024, sygnalizacja jest obowiązkowym komponentem każdej aplikacji WebRTC, ponieważ protokół nie określa konkretnego sposobu wymiany komunikatów sygnalizacyjnych.
Najważniejsze
Signaling Server — to usługa sieciowa odpowiedzialna za koordynację procesu nawiązywania połączenia WebRTC między dwoma lub więcej peerami. Nie przesyła danych multimedialnych (audio, wideo, danych kanałów DataChannel), a jedynie informacje pomocnicze niezbędne do wykrywania peerów i uzgadniania parametrów połączenia. Po pomyślnym ustanowieniu kanału P2P Signaling Server może nie być już potrzebny, ale w niektórych architekturach pozostaje do późniejszej wymiany sygnałów (np. zakończenie połączenia, dodawanie uczestników).
Architektura sygnalizacji obejmuje trzy komponenty: Signaling Server, Signal Channel (protokół transportowy między klientem a serwerem) oraz API klienckie (zwykle wbudowane w stos WebRTC przeglądarki). Specyfikacja WebRTC (W3C, 2024) celowo nie standaryzuje protokołu sygnalizacyjnego — programiści mogą wybrać dowolny transport odpowiedni dla ich aplikacji. To elastyczne rozwiązanie pozwala na użycie WebSocket dla aplikacji webowych, XMPP dla systemów czatowych lub SIP do integracji z infrastrukturą telekomunikacyjną.
Przed ustanowieniem połączenia WebRTC peery muszą wymienić się trzema typami komunikatów: session description (offer i answer), ICE candidates oraz informacją o zakończeniu/modyfikacji sesji. Signaling Server routuje te komunikaty między peerami, używając identyfikatorów pokoi lub użytkowników do adresacji. Standardowy wzorzec — utworzenie „pokoju”, do którego podłączają się dwaj uczestnicy, a serwer retransmituje komunikaty od każdego uczestnika tylko jego rozmówcy.
Signaling Server realizuje następujący typowy protokół ustanawiania połączenia WebRTC. Peery łączą się z serwerem przez WebSocket (lub inny transport) i rejestrują się w pokoju. Peer A (inicjator) tworzy offer (opis SDP wychodzącego strumienia medialnego) przez RTCPeerConnection.createOffer(), ustawia go jako local description i wysyła na Signaling Server. Serwer retransmituje offer do peera B. Peer B otrzymuje offer, ustawia go jako remote description, tworzy answer przez createAnswer(), ustawia go jako local description i wysyła z powrotem przez serwer. Ten proces nazywa się SDP Offer/Answer.
Równolegle z wymianą SDP każdy peer zbiera ICE candidates (host, srflx, relay) i wysyła je przez Signaling Server do drugiego peera. Zdalny peer dodaje otrzymanych kandydatów przez RTCPeerConnection.addIceCandidate(). Proces ICE sprawdza wszystkie kombinacje kandydatów w celu znalezienia działającej ścieżki. Po znalezieniu działającej ścieżki (zwykle w ciągu 1–5 sekund), ruch medialny zaczyna być przesyłany bezpośrednio między peerami, a Signaling Server nie uczestniczy już w transmisji danych — jego rola kończy się do następnego zdarzenia usługowego (zakończenie połączenia, zmiana jakości strumienia).
Do adresacji komunikatów Signaling Server używa mechanizmu pokoi (rooms) lub kanałów. Każda nowa sesja WebRTC tworzy unikalny pokój z identyfikatorem (zwykle UUID). Inicjator tworzy pokój i oczekuje na podłączenie drugiego peera. Drugi peer łączy się z pokojem po ID otrzymanym przez zewnętrzny kanał (np. link zaproszenia). Serwer utrzymuje mapę pokoi, gdzie każdemu ID odpowiada lista podłączonych klientów. Gdy liczba uczestników osiągnie dwóch, serwer zaczyna retransmitować komunikaty sygnalizacyjne między nimi.
Signaling Server może używać różnych protokołów transportowych, z których każdy ma swoje zalety i wady. Wybór protokołu zależy od typu aplikacji, ograniczeń infrastrukturalnych i wymagań dotyczących kompatybilności. Poniżej przedstawiono najczęściej spotykane protokoły i ich charakterystyki.
| Protokół | Transport | Zalety | Wady |
|---|---|---|---|
| WebSocket | TCP | Pełny dupleks, niskie opóźnienie, wbudowany w przeglądarki | Trudność skalowania, blokowanie przez proxy |
| HTTP/SSE | TCP | Kompatybilność z dowolną infrastrukturą, prostota implementacji | Tylko jednokierunkowy (serwer-klient), wymaga Pollingu |
| XMPP | TCP | Standaryzowany, obsługa uwierzytelniania, rozszerzalny | Zbędny dla prostych scenariuszy, narzut XML |
| SIP | UDP/TCP | Integracja z VoIP i infrastrukturą telefoniczną | Złożony, nienatywny dla przeglądarek |
| MQTT | TCP | Lekki, działa w środowiskach IoT, publish/subscribe | Wymaga brokera, dodatkowe opóźnienie |
WebSocket jest najpopularniejszym protokołem dla Signaling Server w aplikacjach webowych. Zapewnia komunikację pełnodupleksową, co jest ważne dla asynchronicznej wymiany SDP i ICE kandydatów, i jest natywnie obsługiwany przez wszystkie nowoczesne przeglądarki przez WebSocket API. Implementacja serwerowa WebSocket jest dostępna na wszystkich popularnych platformach (Node.js, Python, Java, Go). Dla aplikacji z milionami użytkowników stosuje się skalowalne rozwiązania WebSocket oparte na Redis Pub/Sub lub Kafka do synchronizacji między instancjami Signaling Server.
Rozważmy prostą implementację Signaling Server w Node.js z wykorzystaniem biblioteki ws (WebSocket) i wbudowanego serwera HTTP. Serwer obsługuje rejestrację użytkowników, tworzenie pokoi i retransmisję komunikatów między uczestnikami.
const WebSocket = require("ws");
const server = new WebSocket.Server({ port: 8080 });
const rooms = new Map();
server.on("connection", (ws) => {
ws.roomId = null;
ws.on("message", (data) => {
const msg = JSON.parse(data);
switch (msg.type) {
case "join":
handleJoin(ws, msg.roomId);
break;
case "offer":
case "answer":
case "ice-candidate":
relayToPeer(ws, msg);
break;
case "leave":
handleLeave(ws);
break;
}
});
ws.on("close", () => handleLeave(ws));
});
function handleJoin(ws, roomId) {
if (!rooms.has(roomId)) {
rooms.set(roomId, []);
}
const room = rooms.get(roomId);
room.push(ws);
ws.roomId = roomId;
if (room.length === 2) {
room[0].send(JSON.stringify({ type: "peer-joined" }));
room[1].send(JSON.stringify({ type: "peer-joined" }));
}
}
function relayToPeer(sender, msg) {
const room = rooms.get(sender.roomId);
if (!room) return;
room.forEach(peer => {
if (peer !== sender && peer.readyState === WebSocket.OPEN) {
peer.send(JSON.stringify(msg));
}
});
}
function handleLeave(ws) {
if (!ws.roomId) return;
const room = rooms.get(ws.roomId);
if (!room) return;
const idx = room.indexOf(ws);
if (idx !== -1) room.splice(idx, 1);
if (room.length === 0) rooms.delete(ws.roomId);
}
Ten Signaling Server implementuje podstawową funkcjonalność: dołączanie do pokoju, retransmisję komunikatów WebRTC (offer, answer, ice-candidate) między dwoma peerami oraz zarządzanie rozłączeniami. Serwer używa Map do przechowywania pokoi z podłączonymi klientami WebSocket. Funkcja relayToPeer wysyła komunikat do wszystkich uczestników pokoju z wyjątkiem nadawcy. Dla produkcji należy dodać potwierdzenie typów komunikatów, obsługę błędów parsowania JSON oraz mechanizm heartbeat do wykrywania zerwanych połączeń.
Po stronie klienta Signaling Server integruje się przez WebSocket API przeglądarki. Klient nawiązuje połączenie z serwerem, wysyła żądanie dołączenia do pokoju, a następnie przetwarza przychodzące komunikaty WebRTC, przekazując je do RTCPeerConnection przez setRemoteDescription() i addIceCandidate(). Kod kliencki również wysyła na serwer swoje SDP i ICE kandydatów, otrzymane z RTCPeerConnection przez zdarzenia onicecandidate i po utworzeniu offer/answer.
Signaling Server przesyła dwa kluczowe typy metadanych: SDP (Session Description Protocol) i ICE candidates. SDP opisuje parametry strumienia medialnego — kodeki, częstotliwość próbkowania, liczbę kanałów, kierunek transmisji (sendrecv, sendonly, recvonly, inactive). ICE candidates zawierają adresy sieciowe (lokalne, uzyskane z STUN, przekaźnikowe z TURN), pod którymi peer może być dostępny do połączenia.
SDP jest przedstawione w formacie tekstowym zawierającym sesje i sekcje medialne. Część sesyjna opisuje ogólne parametry (identyfikator sesji, wersja, nazwa), sekcje medialne — każdy strumień medialny (audio, wideo, DataChannel) z jego kodekiem, portem i protokołem. ICE kandydaci zawierają foundation (identyfikator do grupowania), priority, adres IP, port, typ (host, srflx, relay) i protokół (UDP, TCP). Każdy candidate zawiera również atrybut ufrag (username fragment), łączący go z konkretnym procesem ICE.
Trickle ICE znacznie przyspiesza ustanawianie połączenia WebRTC. Zamiast czekać na pełne zebranie wszystkich ICE kandydatów (co może zajmować 2–10 sekund w złożonych sieciach), każdy kandydat jest wysyłany na Signaling Server natychmiast po wykryciu. Zdalny peer otrzymuje kandydata i natychmiast rozpoczyna sprawdzanie połączenia przez framework ICE. Skraca to czas ustanawiania połączenia do 500–1500 ms w większości przypadków.
Często zadawane pytania
Signaling Server — to „koordynator” przed rozmową. Pomaga dwóm urządzeniom znaleźć się nawzajem i uzgodnić, jak będą się komunikować. Gdy urządzenia się „poznają” i uzgodnią, serwer nie jest już potrzebny — komunikują się bezpośrednio.
WebRTC nie określa protokołu sygnalizacji, aby programiści mogli wybrać najbardziej odpowiedni transport. Przeglądarka nie ma wbudowanego mechanizmu wykrywania innych użytkowników — to zadanie rozwiązuje Signaling Server. Służy jako „listonosz”, przekazując zaproszenia i ustawienia połączenia między uczestnikami rozmowy.
WebSocket — optymalny wybór dla większości aplikacji webowych: pełnodupleksowy, natywnie obsługiwany przez przeglądarki, prosty w implementacji. Do integracji z istniejącą infrastrukturą VoIP wybierz SIP. Do aplikacji czatowych z bogatymi funkcjami — XMPP. Dla scenariuszy IoT — MQTT.
Do skalowania Signaling Server używaj skalowania poziomego z synchronizacją przez Redis Pub/Sub lub Kafka. Każda instancja serwera obsługuje swoją część połączeń WebSocket, a do komunikacji między serwerami używana jest wspólna szyna danych. To podejście pozwala obsługiwać miliony jednoczesnych sesji sygnalizacyjnych.
Signaling Server jest krytyczny tylko na etapie nawiązywania połączenia. Jeśli serwer jest tymczasowo niedostępny, aktywne połączenia WebRTC są kontynuowane — ruch medialny przebiega bezpośrednio między peerami. Problem pojawia się tylko przy próbie nawiązania nowego połączenia. Dla niezawodności używaj klastrowania serwerów i zapasowych kanałów sygnalizacji.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również