Создайте серверные службы, необходимые для приложения WebRTC.

Что такое сигнализация?

Сигнализация – это процесс координации общения. Чтобы приложение WebRTC могло установить вызов, его клиентам необходимо обменяться следующей информацией:

  • Сообщения управления сеансом, используемые для открытия или закрытия связи.
  • Сообщения об ошибках
  • Метаданные мультимедиа, такие как кодеки, настройки кодека, пропускная способность и типы мультимедиа.
  • Ключевые данные, используемые для установления безопасных соединений
  • Сетевые данные, такие как IP-адрес и порт хоста, видимые из внешнего мира.

Для этого процесса сигнализации необходим способ, позволяющий клиентам передавать сообщения туда и обратно. Этот механизм не реализован API-интерфейсами WebRTC. Вам нужно построить его самостоятельно. Далее в этой статье вы узнаете, как создать службу сигнализации. Однако сначала вам понадобится немного контекста.

Почему сигнализация не определяется WebRTC?

Чтобы избежать избыточности и максимизировать совместимость с существующими технологиями, методы и протоколы сигнализации не указаны в стандартах WebRTC. Этот подход описан протоколом установления сеанса JavaScript (JSEP) :

Архитектура JSEP также позволяет браузеру не сохранять состояние, то есть функционировать как сигнальный конечный автомат. Это было бы проблематично, если бы, например, данные сигнализации терялись при каждой перезагрузке страницы. Вместо этого состояние сигнализации можно сохранить на сервере.

Схема архитектуры JSEP
JSEP-архитектура

JSEP требует обмена между узлами предложения и ответа — медиа-метаданных, упомянутых выше. Предложения и ответы передаются в формате протокола описания сеанса (SDP), который выглядит следующим образом:

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

Хотите знать, что на самом деле означает вся эта чушь СДП? Взгляните на примеры IETF .

Имейте в виду, что WebRTC спроектирован таким образом, что предложение или ответ можно настроить перед тем, как они будут установлены в качестве локального или удаленного описания, путем редактирования значений в тексте SDP. Например, функцию preferAudioCodec() в appr.tc можно использовать для установки кодека и битрейта по умолчанию. SDP несколько болезненно манипулировать с помощью JavaScript, и ведутся дискуссии о том, должны ли будущие версии WebRTC вместо этого использовать JSON, но есть некоторые преимущества в использовании SDP.

API RTCPeerConnection и сигнализация: предложение, ответ и кандидат

RTCPeerConnection — это API, используемый приложениями WebRTC для создания соединения между узлами и передачи аудио и видео.

Чтобы инициализировать этот процесс, RTCPeerConnection выполняет две задачи:

  • Определите местные условия носителя, такие как разрешение и возможности кодека. Это метаданные, используемые для механизма предложений и ответов.
  • Получите потенциальные сетевые адреса хоста приложения, известные как кандидаты .

Как только эти локальные данные будут получены, ими необходимо обменяться через механизм сигнализации с удаленным узлом.

Представьте, что Алиса пытается позвонить Еве . Вот полный механизм предложения/ответа во всех его кровавых деталях:

  1. Алиса создает объект RTCPeerConnection .
  2. Алиса создает предложение (описание сеанса SDP) с помощью метода createOffer() RTCPeerConnection .
  3. Алиса вызывает setLocalDescription() со своим предложением.
  4. Алиса формулирует предложение и использует механизм сигнализации, чтобы отправить его Еве.
  5. Ева вызывает setRemoteDescription() с предложением Алисы, чтобы ее RTCPeerConnection знал о настройке Алисы.
  6. Ева вызывает функцию createAnswer() , и в ответ на успешный вызов передается описание локального сеанса — ответ Евы.
  7. Ева устанавливает свой ответ как локальное описание, вызывая setLocalDescription() .
  8. Затем Ева использует механизм сигнализации, чтобы отправить свой строковый ответ Алисе.
  9. Алиса устанавливает ответ Евы в качестве описания удаленного сеанса с помощью setRemoteDescription() .

Алисе и Еве также необходимо обменяться сетевой информацией. Выражение «поиск кандидатов» относится к процессу поиска сетевых интерфейсов и портов с использованием фреймворка ICE .

  1. Алиса создает объект RTCPeerConnection с обработчиком onicecandidate .
  2. Обработчик вызывается, когда сетевые кандидаты становятся доступными.
  3. В обработчике Алиса отправляет Еве строковые данные-кандидаты через их сигнальный канал.
  4. Когда Ева получает сообщение о кандидате от Алисы, она вызывает addIceCandidate() чтобы добавить кандидата в описание удаленного узла.

JSEP поддерживает ICE Candidate Trickling , что позволяет вызывающему абоненту постепенно предоставлять кандидатов вызываемому абоненту после первоначального предложения, а вызываемый абонент может начать действовать по вызову и установить соединение, не дожидаясь прибытия всех кандидатов.

Код WebRTC для сигнализации

Следующий фрагмент кода представляет собой пример кода W3C , который суммирует весь процесс передачи сигналов. Код предполагает наличие некоторого механизма сигнализации SignalingChannel . Сигнализация обсуждается более подробно позже.

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

Чтобы увидеть процессы предложения/ответа и обмена кандидатами в действии, посетите сайт simpl.info RTCPeerConnection и посмотрите в журнале консоли пример одностраничного видеочата. Если вам нужно больше, загрузите полный дамп сигналов и статистики WebRTC со страницы about://webrtc-internals в Google Chrome или страницы Opera://webrtc-internals в Opera.

Одноранговое открытие

Это необычный способ спросить: «Как мне найти кого-нибудь, с кем можно поговорить?»

Для телефонных звонков у вас есть телефонные номера и справочники. Для онлайн-видеочата и обмена сообщениями вам нужны системы управления идентификацией и присутствием, а также средства, позволяющие пользователям инициировать сеансы. Приложениям WebRTC нужен способ, с помощью которого клиенты смогут сигнализировать друг другу о том, что они хотят начать вызов или присоединиться к нему.

Механизмы обнаружения одноранговых узлов не определяются WebRTC, и здесь вы не вдаваетесь в параметры. Этот процесс может быть таким же простым, как отправка URL-адреса по электронной почте или сообщением. В приложениях для видеочата, таких как Talky , tawk.to и Browser Meeting , вы приглашаете людей на звонок, делясь специальной ссылкой. Разработчик Крис Болл разработал интригующий эксперимент с бессерверным WebRTC , который позволяет участникам вызовов WebRTC обмениваться метаданными с помощью любой службы обмена сообщениями, которая им нравится, например, мгновенных сообщений, электронной почты или почтового голубя.

Как создать службу сигнализации?

Еще раз повторю: протоколы и механизмы сигнализации не определены стандартами WebRTC. Что бы вы ни выбрали, вам понадобится промежуточный сервер для обмена сигнальными сообщениями и данными приложений между клиентами. К сожалению, веб-приложение не может просто кричать в Интернет: «Соедините меня с моим другом!»

К счастью, сигнальные сообщения невелики и в основном передаются в начале вызова. При тестировании сеанса видеочата с помощью appr.tc служба сигнализации обработала в общей сложности около 30–45 сообщений с общим размером всех сообщений около 10 КБ.

Службы сигнализации WebRTC не только относительно нетребовательны с точки зрения пропускной способности, но и не потребляют много ресурсов обработки или памяти, поскольку им нужно только ретранслировать сообщения и сохранять небольшой объем данных о состоянии сеанса, например о том, какие клиенты подключены.

Отправка сообщений с сервера клиенту

Служба сообщений для сигнализации должна быть двунаправленной: клиент-сервер и сервер-клиент. Двунаправленная связь противоречит модели запроса/ответа HTTP клиент/сервер, но на протяжении многих лет были разработаны различные хаки, такие как длительный опрос, для передачи данных из службы, работающей на веб-сервере, в веб-приложение, работающее в браузере.

Совсем недавно широкое распространение получил EventSource API . Это позволяет отправлять события, отправляемые сервером, — данные, отправляемые с веб-сервера клиенту браузера через HTTP. EventSource предназначен для одностороннего обмена сообщениями, но его можно использовать в сочетании с XHR для создания службы обмена сигнальными сообщениями. Служба сигнализации передает сообщение от вызывающего абонента, доставленное по запросу XHR, направляя его через EventSource вызываемому абоненту.

WebSocket — более естественное решение, предназначенное для полнодуплексной связи клиент-сервер — сообщения могут передаваться в обоих направлениях одновременно. Одним из преимуществ службы сигнализации, созданной с использованием чистого WebSocket или событий, отправляемых сервером ( EventSource ), является то, что серверная часть для этих API может быть реализована на различных веб-платформах, общих для большинства пакетов веб-хостинга для таких языков, как PHP, Python и Ruby.

Все современные браузеры, кроме Opera Mini, поддерживают WebSocket и, что более важно, все браузеры, поддерживающие WebRTC, также поддерживают WebSocket, как на настольных компьютерах, так и на мобильных устройствах. TLS следует использовать для всех соединений, чтобы гарантировать невозможность перехвата сообщений в незашифрованном виде, а также для уменьшения проблем с обходом прокси-сервера . (Дополнительную информацию о WebSocket и обходе прокси см. в главе WebRTC в книге Ильи Григорика «Высокопроизводительная браузерная сеть» .)

Также можно обрабатывать сигнализацию, заставляя клиентов WebRTC неоднократно опрашивать сервер обмена сообщениями через Ajax, но это приводит к множеству избыточных сетевых запросов, что особенно проблематично для мобильных устройств. Даже после установления сеанса одноранговым узлам необходимо опрашивать сигнальные сообщения в случае изменений или завершения сеанса другими узлами. В примере приложения WebRTC Book используется этот вариант с некоторой оптимизацией частоты опроса.

Масштабная сигнализация

Хотя служба сигнализации потребляет относительно небольшую полосу пропускания и ЦП на каждого клиента, серверам сигнализации для популярного приложения может потребоваться обработка большого количества сообщений из разных мест с высоким уровнем параллелизма. Приложениям WebRTC, которые получают большой трафик, нужны серверы сигнализации, способные выдерживать значительную нагрузку. Не будем вдаваться здесь в подробности, но существует ряд вариантов для высокопроизводительного обмена сообщениями в больших объемах, включая следующие:

  • eXtensible Messaging and Presence Protocol (XMPP), первоначально известный как Jabber — протокол, разработанный для обмена мгновенными сообщениями, который можно использовать для сигнализации (Серверные реализации включают ejabberd и Openfire . Клиенты JavaScript, такие как Strope.js , используют BOSH для эмуляции двунаправленной потоковой передачи, но по разным причинам BOSH может быть не таким эффективным, как WebSocket, и по тем же причинам может плохо масштабироваться.) (На касательно, Jingle — это расширение XMPP, обеспечивающее передачу голоса и видео. Проект WebRTC использует сетевые и транспортные компоненты из библиотеки libjingle — реализации Jingle на C++.)

  • Библиотеки с открытым исходным кодом, такие как ZeroMQ (используемые TokBox для службы Rumor ) и OpenMQ ( NullMQ применяет концепции ZeroMQ к веб-платформам, используя протокол STOMP через WebSocket.)

  • Коммерческие платформы облачных сообщений, использующие WebSocket (хотя они могут прибегать к длинному опросу), такие как Pusher , Kaazing и PubNub (PubNub также имеет API для WebRTC ).

  • Коммерческие платформы WebRTC, такие как vLine.

( Руководство по веб-технологиям реального времени, написанное разработчиком Филом Леггеттером, содержит полный список служб и библиотек обмена сообщениями.)

Создайте службу сигнализации с помощью Socket.io на Node.

Ниже приведен код простого веб-приложения, которое использует службу сигнализации, созданную с помощью Socket.io на Node . Дизайн Socket.io упрощает создание службы обмена сообщениями, а Socket.io особенно подходит для сигнализации WebRTC благодаря встроенной концепции комнат. Этот пример не предназначен для масштабирования в качестве службы сигнализации производственного уровня, но его легко понять относительно небольшому числу пользователей.

Socket.io использует WebSocket с резервными вариантами: длинный опрос AJAX, многочастная потоковая передача AJAX, Forever Iframe и опрос JSONP. Он был портирован на различные серверные части, но, пожалуй, наиболее известен своей версией Node, использованной в этом примере.

В этом примере нет WebRTC. Он предназначен только для того, чтобы показать, как встроить сигнализацию в веб-приложение. Просмотрите журнал консоли, чтобы узнать, что происходит, когда клиенты присоединяются к комнате и обмениваются сообщениями. Эта лаборатория кода WebRTC дает пошаговые инструкции по интеграции этого в полноценное приложение для видеочата WebRTC.

Вот клиентский 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>

Вот файл JavaScript main.js , на который есть ссылка в клиенте:

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

Вот полное серверное приложение:

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

  });

});

(Для этого вам не нужно изучать Node-Static. Он просто используется в этом примере.)

Чтобы запустить это приложение на локальном хосте, вам необходимо установить Node, Socket.IO и node-static . Node можно загрузить с Node.js (установка проста и быстра). Чтобы установить Socket.IO и node-static, запустите Node Package Manager из терминала в каталоге вашего приложения:

npm install socket.io
npm install node-static

Чтобы запустить сервер, запустите следующую команду из терминала в каталоге вашего приложения:

node server.js

В браузере откройте localhost:2013 . Откройте новую вкладку или окно в любом браузере и снова откройте localhost:2013 . Чтобы увидеть, что происходит, проверьте консоль. В Chrome и Opera вы можете получить доступ к консоли через инструменты разработчика Google Chrome, нажав Ctrl+Shift+J (или Command+Option+J на Mac).

Какой бы подход вы ни выбрали для сигнализации, ваше серверное и клиентское приложение — как минимум — должно предоставлять услуги, аналогичные этому примеру.

Сигнальные ошибки

  • RTCPeerConnection не начнет собирать кандидатов, пока не будет вызвана setLocalDescription() . Это предусмотрено проектом JSEP IETF .
  • Воспользуйтесь преимуществами Trickle ICE. Вызовите addIceCandidate() , как только прибудут кандидаты.

Готовые серверы сигнализации

Если вы не хотите создавать свои собственные, есть несколько доступных серверов сигнализации WebRTC, которые используют Socket.IO, как в предыдущем примере, и интегрированы с клиентскими библиотеками JavaScript WebRTC:

  • webRTC.io — одна из первых библиотек абстракции для WebRTC.
  • Signalmaster — это сервер сигнализации, созданный для использования с клиентской библиотекой JavaScript SimpleWebRTC .

Если вы вообще не хотите писать какой-либо код, можно приобрести полноценные коммерческие платформы WebRTC от таких компаний, как vLine , OpenTok и Asterisk .

Для справки, Эрикссон создал сервер сигнализации с использованием PHP на Apache на заре WebRTC. Сейчас это несколько устарело, но стоит посмотреть код, если вы рассматриваете что-то подобное.

Безопасность сигнализации

«Безопасность — это искусство ничего не делать».

Салман Рушди

Шифрование является обязательным для всех компонентов WebRTC.

Однако механизмы сигнализации не определены стандартами WebRTC, поэтому обеспечение безопасности сигнализации зависит от вас. Если злоумышленнику удастся перехватить сигнализацию, он сможет остановить сеансы, перенаправить соединения, а также записать, изменить или внедрить контент.

Важнейшим фактором защиты сигнализации является использование безопасных протоколов — HTTPS и WSS (например, TLS), которые гарантируют, что сообщения не могут быть перехвачены в незашифрованном виде. Кроме того, будьте осторожны, чтобы не транслировать сигнальные сообщения так, чтобы к ним могли получить доступ другие вызывающие абоненты, использующие тот же сервер сигнализации.

После сигнализации: используйте ICE для работы с NAT и межсетевыми экранами.

Для передачи сигналов метаданных приложения WebRTC используют промежуточный сервер, но для фактической потоковой передачи мультимедиа и данных после установления сеанса RTCPeerConnection пытается подключить клиентов напрямую или в одноранговой сети.

В более простом мире каждая конечная точка WebRTC имела бы уникальный адрес, которым она могла бы обмениваться с другими узлами для прямой связи.

Простое одноранговое соединение
Мир без NAT и межсетевых экранов

В действительности, большинство устройств находятся за одним или несколькими уровнями NAT , на некоторых установлено антивирусное программное обеспечение, которое блокирует определенные порты и протоколы, а многие находятся за прокси-серверами и корпоративными брандмауэрами. Фактически межсетевой экран и NAT могут быть реализованы одним и тем же устройством, например домашним WIFI-маршрутизатором.

Пиры за NAT и межсетевыми экранами
Реальный мир

Приложения WebRTC могут использовать платформу ICE для преодоления сложностей реальных сетей. Чтобы это произошло, ваше приложение должно передавать URL-адреса сервера ICE в RTCPeerConnection , как описано в этой статье.

ICE пытается найти лучший путь для соединения одноранговых узлов. Он пробует все возможности параллельно и выбирает наиболее эффективный вариант, который работает. ICE сначала пытается установить соединение, используя адрес хоста, полученный из операционной системы устройства и сетевой карты. Если это не удается (что происходит для устройств за NAT), ICE получает внешний адрес с помощью сервера STUN, а если это не удается, трафик направляется через сервер ретрансляции TURN.

Другими словами, сервер STUN используется для получения адреса внешней сети, а серверы TURN используются для ретрансляции трафика в случае сбоя прямого (однорангового) соединения.

Каждый сервер TURN поддерживает STUN. Сервер TURN — это сервер STUN с дополнительными встроенными функциями ретрансляции. ICE также справляется со сложностями настройки NAT. В действительности для пробивания отверстий NAT может потребоваться нечто большее, чем просто общедоступный IP-адрес порта.

URL-адреса для серверов STUN и/или TURN (необязательно) указываются приложением WebRTC в объекте конфигурации iceServers , который является первым аргументом конструктора RTCPeerConnection . Для appr.tc это значение выглядит следующим образом:

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

Как только RTCPeerConnection получит эту информацию, волшебство ICE произойдет автоматически. RTCPeerConnection использует структуру ICE для определения наилучшего пути между узлами, работая при необходимости с серверами STUN и TURN.

Оглушение

NAT предоставляет устройству IP-адрес для использования в частной локальной сети, но этот адрес нельзя использовать извне. Без публичного адреса узлы WebRTC не смогут общаться. Чтобы обойти эту проблему, WebRTC использует STUN .

Серверы STUN находятся в общедоступном Интернете и имеют одну простую задачу — проверить адрес IP:port входящего запроса (из приложения, работающего за NAT) и отправить этот адрес обратно в качестве ответа. Другими словами, приложение использует STUN-сервер для обнаружения своего IP-порта с публичной точки зрения. Этот процесс позволяет узлу WebRTC получить общедоступный адрес для себя, а затем передать его другому узлу через механизм сигнализации для установки прямого соединения. (На практике разные NAT работают по-разному, и может существовать несколько уровней NAT, но принцип остается тем же.)

STUN-серверам не нужно много делать или много запоминать, поэтому STUN-серверы с относительно низкими характеристиками могут обрабатывать большое количество запросов.

Большинство вызовов WebRTC успешно устанавливают соединение с использованием STUN — 86% по данным Webrtcstats.com , хотя это может быть меньше для вызовов между узлами за брандмауэрами и сложными конфигурациями NAT.

Одноранговое соединение с использованием STUN-сервера
Использование серверов STUN для получения общедоступных IP-адресов портов

ПОВЕРНУТЬ

RTCPeerConnection пытается установить прямую связь между узлами через UDP. Если это не удается, RTCPeerConnection прибегает к TCP. Если это не поможет, серверы TURN можно использовать в качестве запасного варианта, передавая данные между конечными точками.

Еще раз повторю: TURN используется для ретрансляции аудио, видео и потоковой передачи данных между узлами, а не для передачи данных!

Серверы TURN имеют публичные адреса, поэтому узлы могут связаться с ними, даже если узлы находятся за брандмауэрами или прокси-серверами. У серверов TURN концептуально простая задача — ретранслировать поток. Однако, в отличие от серверов STUN, они по своей природе потребляют большую полосу пропускания. Другими словами, серверы TURN должны быть более мощными.

Одноранговое соединение с использованием STUN-сервера
Полный Монти: ОГЛУШЕНИЕ, ПОВОРОТ и подача сигналов.

На этой диаграмме показана функция ПОВОРОТ в действии. Использование чистого STUN не удалось, поэтому каждый узел прибегает к использованию сервера TURN.

Развертывание серверов STUN и TURN

Для тестирования Google запускает общедоступный сервер STUN stun.l.google.com:19302, используемый appr.tc. Для производственной службы STUN/TURN используйте сервер rfc5766-turn-server. Исходный код серверов STUN и TURN доступен на GitHub , там же можно найти ссылки на несколько источников информации об установке сервера. Также доступен образ виртуальной машины для Amazon Web Services .

Возобновлен альтернативный сервер TURN, доступный в виде исходного кода , а также для AWS. Ниже приведены инструкции по настройке возврата средств в Compute Engine.

  1. При необходимости откройте брандмауэр для tcp=443, udp/tcp=3478.
  2. Создайте четыре экземпляра, по одному для каждого общедоступного IP-адреса, стандартный образ Ubuntu 12.06.
  3. Настройте конфигурацию локального брандмауэра (разрешите ЛЮБОЙ из ЛЮБОГО).
  4. Инструменты установки: shell sudo apt-get install make sudo apt-get install gcc
  5. Установите libre с сайта creytiv.com/re.html .
  6. Получите возврат средств с сайта creytiv.com/restund.html и распакуйте./
  7. wget hancke.name/restund-auth.patch и примените patch -p1 < restund-auth.patch .
  8. Запустите make , sudo make install для libre и выполните возврат.
  9. Адаптируйте restund.conf к своим потребностям (замените IP-адреса и убедитесь, что он содержит тот же общий секретный ключ) и скопируйте его в /etc .
  10. Скопируйте restund/etc/restund в /etc/init.d/ .
  11. Настроить возврат:
    1. Установите LD_LIBRARY_PATH .
    2. Скопируйте restund.conf в /etc/restund.conf .
    3. Настройте restund.conf для использования правильного 10. IP-адреса.
  12. Запустить возврат
  13. Проверьте использование клиента stund с удаленного компьютера: ./client IP:port

Больше, чем общение один на один: Multiparty WebRTC

Вы также можете ознакомиться с предложенным Джастином Уберти стандартом IETF для REST API для доступа к службам TURN .

Легко представить варианты использования потоковой передачи мультимедиа, выходящие за рамки простого индивидуального вызова. Например, видеоконференция между группой коллег или публичное мероприятие с одним докладчиком и сотнями или миллионами зрителей.

Приложение WebRTC может использовать несколько RTCPeerConnections, чтобы каждая конечная точка подключалась к любой другой конечной точке в конфигурации сетки. Этот подход используется в таких приложениях, как talky.io , и он отлично работает для небольшой группы пользователей. Кроме того, обработка и потребление полосы пропускания становятся чрезмерными, особенно для мобильных клиентов.

Сетка: небольшой N-сторонний вызов
Полноячеистая топология: все подключены ко всем.

В качестве альтернативы приложение WebRTC может выбрать одну конечную точку для распределения потоков всем остальным в звездообразной конфигурации. Также можно было бы запустить конечную точку WebRTC на сервере и создать собственный механизм перераспределения ( пример клиентского приложения предоставлен сайтом webrtc.org).

Начиная с Chrome 31 и Opera 18, MediaStream из одного RTCPeerConnection может использоваться в качестве входных данных для другого. Это может обеспечить более гибкую архитектуру, поскольку позволяет веб-приложению обрабатывать маршрутизацию вызовов, выбирая, к какому другому узлу подключаться. Чтобы увидеть это в действии, см. примеры WebRTC Ретрансляция одноранговых соединений и примеры WebRTC с несколькими одноранговыми соединениями .

Многоточечный блок управления

Лучшим вариантом для большого количества конечных точек является использование Multipoint Control Unit (MCU). Это сервер, который работает как мост для распределения мультимедиа между большим количеством участников. MCU могут работать с различными разрешениями, кодеками и частотой кадров во время видеоконференции; обрабатывать транскодирование; выполнить выборочную пересылку потока; и микшировать или записывать аудио и видео. При многосторонних вызовах необходимо учитывать ряд проблем, в частности, как отображать несколько видеовходов и микшировать звук из нескольких источников. Облачные платформы, такие как vLine , также пытаются оптимизировать маршрутизацию трафика.

Можно купить полный аппаратный пакет MCU или собрать свой собственный.

Cisco MCU5300, вид сзади
Задняя часть Cisco MCU

Доступно несколько вариантов программного обеспечения MCU с открытым исходным кодом. Например, Licode (ранее известная как Lynckia) производит MCU с открытым исходным кодом для WebRTC. В OpenTok есть Mantis .

За пределами браузеров: VoIP, телефоны и обмен сообщениями

Стандартизированный характер WebRTC позволяет установить связь между приложением WebRTC, работающим в браузере, и устройством или платформой, работающим на другой коммуникационной платформе, например телефоне или системе видеоконференцсвязи.

SIP — это протокол сигнализации, используемый системами VoIP и видеоконференцсвязи. Чтобы обеспечить связь между веб-приложением WebRTC и SIP-клиентом, например системой видеоконференций, WebRTC необходим прокси-сервер для передачи сигналов. Сигнализация должна проходить через шлюз, но после установления связи трафик SRTP (видео и аудио) может передаваться напрямую между узлами.

Коммутируемая телефонная сеть общего пользования (PSTN) — это сеть с коммутацией каналов всех «старых простых» аналоговых телефонов. Для вызовов между веб-приложениями WebRTC и телефонами трафик должен проходить через шлюз PSTN. Аналогичным образом, веб-приложениям WebRTC необходим промежуточный сервер XMPP для связи с конечными точками Jingle , такими как IM-клиенты. Jingle был разработан Google как расширение XMPP для поддержки передачи голоса и видео в службах обмена сообщениями. Текущие реализации WebRTC основаны на библиотеке C++ libjingle — реализации Jingle, изначально разработанной для Talk.

Ряд приложений, библиотек и платформ используют возможности WebRTC для связи с внешним миром:

  • sipML5 : SIP-клиент JavaScript с открытым исходным кодом.
  • jsSIP : библиотека SIP JavaScript.
  • Phono : API телефона на JavaScript с открытым исходным кодом, созданный в виде плагина.
  • Zingaya : встраиваемый виджет телефона
  • Twilio : голос и обмен сообщениями
  • Уберконференция : конференц-связь

Разработчики sipML5 также создали шлюз webrtc2sip . Tethr и Tropo продемонстрировали структуру для экстренной связи «в портфеле» с использованием соты OpenBTS для обеспечения связи между функциональными телефонами и компьютерами через WebRTC. Это телефонная связь без оператора связи!

Узнать больше

Лаборатория кода WebRTC предоставляет пошаговые инструкции по созданию приложения для видео- и текстового чата с использованием службы сигнализации Socket.io, работающей на Node.

Презентация Google I/O WebRTC 2013 года с техническим руководителем WebRTC Джастином Уберти

Презентация Криса Уилсона SFHTML5 — Введение в приложения WebRTC

350-страничная книга WebRTC: API и протоколы RTCWEB в сети HTML5 реального времени содержит много подробностей о путях передачи данных и сигналов, а также включает ряд подробных диаграмм топологии сети.

WebRTC и сигнализация: чему нас научили два года — сообщение в блоге TokBox о том, почему исключение сигнализации из спецификации было хорошей идеей

Практическое руководство Бена Стронга по созданию приложений WebRTC содержит много информации о топологиях и инфраструктуре WebRTC.

Глава WebRTC в книге Ильи Григорика « Высокопроизводительная браузерная сеть» подробно рассматривает архитектуру WebRTC, варианты использования и производительность.

,

Что такое сигнализация?

Сигнализация – это процесс координации общения. Чтобы приложение WebRTC могло установить вызов, его клиентам необходимо обменяться следующей информацией:

  • Сообщения управления сеансом, используемые для открытия или закрытия связи.
  • Сообщения об ошибках
  • Метаданные мультимедиа, такие как кодеки, настройки кодека, пропускная способность и типы мультимедиа.
  • Ключевые данные, используемые для установления безопасных соединений
  • Сетевые данные, такие как IP-адрес и порт хоста, видимые из внешнего мира.

Для этого процесса сигнализации необходим способ, позволяющий клиентам передавать сообщения туда и обратно. Этот механизм не реализован API-интерфейсами WebRTC. Вам нужно построить его самостоятельно. Далее в этой статье вы узнаете, как создать службу сигнализации. Однако сначала вам понадобится немного контекста.

Почему сигнализация не определяется WebRTC?

Чтобы избежать избыточности и максимизировать совместимость с существующими технологиями, методы и протоколы сигнализации не указаны в стандартах WebRTC. Этот подход описан протоколом установления сеанса JavaScript (JSEP) :

Архитектура JSEP также позволяет браузеру не сохранять состояние, то есть функционировать как сигнальный конечный автомат. Это было бы проблематично, если бы, например, данные сигнализации терялись при каждой перезагрузке страницы. Вместо этого состояние сигнализации можно сохранить на сервере.

Схема архитектуры JSEP
JSEP-архитектура

JSEP требует обмена между узлами предложения и ответа — медиа-метаданных, упомянутых выше. Предложения и ответы передаются в формате протокола описания сеанса (SDP), который выглядит следующим образом:

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

Хотите знать, что на самом деле означает вся эта чушь СДП? Взгляните на примеры IETF .

Имейте в виду, что WebRTC спроектирован таким образом, что предложение или ответ можно настроить перед тем, как они будут установлены в качестве локального или удаленного описания, путем редактирования значений в тексте SDP. Например, функцию preferAudioCodec() в appr.tc можно использовать для установки кодека и битрейта по умолчанию. SDP несколько болезненно манипулировать с помощью JavaScript, и ведутся дискуссии о том, должны ли будущие версии WebRTC вместо этого использовать JSON, но есть некоторые преимущества в использовании SDP.

API RTCPeerConnection и сигнализация: предложение, ответ и кандидат

RTCPeerConnection — это API, используемый приложениями WebRTC для создания соединения между узлами и передачи аудио и видео.

Чтобы инициализировать этот процесс, RTCPeerConnection выполняет две задачи:

  • Определите местные условия носителя, такие как разрешение и возможности кодека. Это метаданные, используемые для механизма предложений и ответов.
  • Получите потенциальные сетевые адреса хоста приложения, известные как кандидаты .

Как только эти локальные данные будут получены, ими необходимо обменяться через механизм сигнализации с удаленным узлом.

Представьте, что Алиса пытается позвонить Еве . Вот полный механизм предложения/ответа во всех его кровавых деталях:

  1. Алиса создает объект RTCPeerConnection .
  2. Алиса создает предложение (описание сеанса SDP) с помощью метода createOffer() RTCPeerConnection .
  3. Алиса вызывает setLocalDescription() со своим предложением.
  4. Алиса формулирует предложение и использует механизм сигнализации, чтобы отправить его Еве.
  5. Eve Calls setRemoteDescription() с предложением Алисы, так что ее RTCPeerConnection знала о настройке Алисы.
  6. Ева называет createAnswer() , и обратный вызов успеха для этого передается описание локального сеанса - Ответ Евы.
  7. Ева устанавливает свой ответ в качестве локального описания, позвонив setLocalDescription() .
  8. Затем Ева использует механизм сигнализации, чтобы отправить свой строгий ответ Алисе.
  9. Алиса устанавливает ответ Евы в качестве описания удаленного сеанса с использованием setRemoteDescription() .

Алиса и Ева также должны обмениваться информацией о сети. Выражение «поиск кандидатов» относится к процессу поиска сетевых интерфейсов и портов с использованием структуры льда .

  1. Алиса создает объект RTCPeerConnection с помощью обработчика onicecandidate .
  2. Обработчик называется, когда кандидаты в сеть становятся доступными.
  3. В обработчике Алиса отправляет строгие данные кандидата в Еву через свой канал сигнализации.
  4. Когда Ева получает сообщение кандидата от Алисы, она называет addIceCandidate() чтобы добавить кандидата в отдаленное описание сверстников.

JSEP поддерживает кандидат в кандидат на ICE , что позволяет вызывающему абоненту постепенно предоставлять кандидатам в Callee после первоначального предложения, и для того, чтобы Callee начала действовать на вызове и устанавливает соединение, не ожидая, когда все кандидаты прибудут.

Код webrtc для сигнализации

Следующий фрагмент кода является примером кода W3C , который суммирует полный процесс сигнализации. Код предполагает существование некоторого механизма сигнализации, SignalingChannel . Сигнализация обсуждается более подробно позже.

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

Чтобы увидеть процессы предложения/ответа и обмена кандидата в действии, см. Simpl.info Rtcpeerconnection и посмотрите журнал консоли для одностраничного примера видеочата. Если вы хотите большего, загрузите полный дамп сигнализации и статистики WEBRTC со страницы ae: // webrtc-internals в Google Chrome или на странице Opera: // webrtc-internals в Opera.

Открытие сверстников

Это причудливый способ спросить: «Как мне найти с кем поговорить?»

Для телефонных звонков у вас есть телефонные номера и каталоги. Для онлайн -видеочата и обмена сообщениями вам нужны системы управления идентификацией и присутствием, а также средство для пользователей для начала сессий. Приложения WEBRTC нуждаются в способе для клиентов, чтобы сигнализировать друг другу, что они хотят начать или присоединиться к вызову.

Механизмы обнаружения сверстников не определяются WEBRTC, и вы не попадаете в варианты здесь. Процесс может быть таким же простым, как электронная почта или обмен сообщениями по URL. Для приложений для видеочата, таких как Talky , Tawk.to и собрание браузера , вы приглашаете людей позвонить, поделившись пользовательской ссылкой. Разработчик Крис Болл построил интригующий эксперимент без сервера-Webrtc , который позволяет участникам вызова WEBRTC обменять метаданные с помощью любых услуг обмена сообщениями, которые им нравятся, например, IM, электронная почта или голубь.

Как вы можете создать сигнальную услугу?

Чтобы повторить, сигнальные протоколы и механизмы не определяются стандартами WEBRTC. Что бы вы ни выбрали, вам нужен промежуточный сервер для обмена сообщениями сигнализации и данных приложений между клиентами. К сожалению, веб -приложение не может просто кричать в Интернет: «Подключите меня к своему другу!»

К счастью, сигнальные сообщения маленькие и в основном обмениваются в начале звонка. При тестировании с App.TC для сеанса видеочата в общей сложности около 30-45 сообщений были обработаны службой сигнализации с общим размером для всех сообщений около 10 КБ.

Помимо относительно нетребования с точки зрения полосы пропускания, сигнальные услуги WEBRTC не потребляют много обработки или памяти, потому что им необходимо только передавать сообщения и сохранять небольшой объем данных состояния сеанса, например, какие клиенты подключены.

Протолкнуть сообщения с сервера к клиенту

Служба сообщений для сигнализации должна быть двунаправленной: клиент для сервера и сервер для клиента. Двунаправленная связь идет против модели запроса/ответа клиента/сервера HTTP, но в течение многих лет были разработаны различные взломы, такие как долгие опросы , чтобы натолкнуть данные с службы, работающего на веб -сервере, в веб -приложение, работающее в браузере.

Совсем недавно API EventSource был широко реализован . Это включает в себя серверные события - данные, отправляемые с веб -сервера в клиент браузера через HTTP. EventSource предназначен для одностороннего обмена сообщениями, но его можно использовать в сочетании с XHR для создания услуги для обмена сигнальными сообщениями. Служба сигнализации передает сообщение от вызывающего абонента, доставленного запросом XHR, путем протокола через EventSource к Callee.

WebSocket - это более натуральное решение, предназначенное для полной дуплексной связи клиента и сервера - сообщения, которые могут течь в обоих направлениях одновременно. Одним из преимуществ сигнальной службы, созданной с помощью Pure WebSocket или серверных событий ( EventSource ), что бэкэнд для этих API может быть реализован на различных веб-структурах, общих для большинства пакетов для веб-хостинга для таких языков, как PHP, Python и Ruby.

Все современные браузеры, кроме Opera Mini, поддерживают WebSocket и, что более важно, все браузеры, которые поддерживают WEBRTC, также поддерживают WebSocket, как на рабочем столе, так и на мобильных устройствах. TLS следует использовать для всех соединений, чтобы убедиться, что сообщения не могут быть перехвачены незашифрованными, а также для уменьшения проблем с проклятием прокси . (Для получения дополнительной информации о WebSocket и Proxy Traversal см. В главе WEBRTC в высокопроизводительных сетевых сетях Ильи Григорика.)

Также возможно обрабатывать сигнализацию, заставив клиентов WEBRTC для неоднократного опроса сервера обмена сообщениями через AJAX, но это приводит к множеству избыточных сетевых запросов, что особенно проблематично для мобильных устройств. Даже после того, как сеанс был создан, сверстники необходимо опросить для передачи сигналов сообщений в случае изменений или завершения сеанса другими сверстниками. Пример приложения книги WEBRTC принимает эту опцию с некоторыми оптимизациями для частоты опроса.

Сигнализация масштаба

Хотя сигнальная услуга потребляет относительно небольшую полосу пропускания и процессор на одного клиента, сигнальные серверы для популярного приложения могут обрабатывать множество сообщений из разных мест с высоким уровнем параллелизма. Приложения WEBRTC, которые получают много трафика, требуют сигнальных серверов, способных обрабатывать значительную нагрузку. Вы не вдаетесь в подробности здесь, но есть ряд вариантов для больших объемов, высокопроизводительных сообщений, включая следующее:

  • eXtensible Messaging and Presence Protocol (XMPP), originally known as Jabber-a protocol developed for instant messaging that can be used for signaling (Server implementations include ejabberd and Openfire . JavaScript clients, such as Strophe.js , use BOSH to emulate bidirectional streaming, but for various reasons , BOSH may not be as efficient as WebSocket and, for the same reasons, may not scale well.) (On a Tangent, Jingle - это расширение XMPP для включения голоса и видео.

  • Библиотеки с открытым исходным кодом, такие как ZeromQ (как используется Tokbox для их службы слухов ) и OpenMQ ( NullMQ применяет концепции ZeromQ к веб -платформам, используя протокол Stomp через WebSocket.)

  • Коммерческие платформы с помощью облака, которые используют WebSocket (хотя они могут вернуться к длительному опросу), такие как Pusher , Kaazing и Pubnub (pubnub также имеет API для Webrtc .)

  • Коммерческие платформы WEBRTC, такие как Vline

(Разработчик Фил Леггеттер в режиме реального времени в режиме реального времени предоставляет комплексный список услуг и библиотек обмена сообщениями.)

Создайте сервис сигнализации с Socket.io на узле

Ниже приведен код для простого веб -приложения, которое использует сервис сигнализации, созданный с помощью socket.io на узле . Дизайн Socket.io позволяет создать услугу для обмена сообщениями, а сокет. Этот пример не предназначен для масштабирования в качестве сервиса передачи сигналов производственного класса, но его простой для понимания для относительно небольшого числа пользователей.

Socket.io использует WebSocket с запасными частями: Ajax Long Offilling, Ajax Multipart Streaming, Forever Iframe и JSONP. Он был перенесен в различные бэкэнды, но, возможно, наиболее известен своей версией узла, используемой в этом примере.

В этом примере нет webrtc. Он предназначен только для того, чтобы показать, как построить сигнализацию в веб -приложение. Посмотрите журнал консоли, чтобы увидеть, что происходит, когда клиенты присоединяются к комнате и обмениваются сообщениями. Этот WEBRTC CodeLab дает пошаговые инструкции о том, как интегрировать это в полное приложение видеочата WEBRTC.

Вот клиент 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>

Вот файл javascript main.js , ссылаясь на клиент:

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

Вот полное приложение для сервера:

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

  });

});

(Вам не нужно узнавать о Node-Static для этого. Это просто используется в этом примере.)

Чтобы запустить это приложение на Localhost, вам необходимо установить узлы, сокет.io и узел-статик . Узел можно загрузить с node.js (установка проста и быстрая). Чтобы установить socket.io и node-static, запустите диспетчер пакетов узлов из терминала в каталоге приложений:

npm install socket.io
npm install node-static

Чтобы запустить сервер, запустите следующую команду из терминала в каталоге ваших приложений:

node server.js

Из вашего браузера Open localhost:2013 . Откройте новую вкладку или окно в любом браузере и снова откройте localhost:2013 . Чтобы увидеть, что происходит, проверьте консоль. В Chrome и Opera вы можете получить доступ к консоли через инструменты разработчика Google Chrome с Ctrl+Shift+J (или Command+Option+J на Mac).

Какой бы подход вы ни выбрали для сигнализации, ваше приложение Backend и Client - по крайней мере - необходимо предоставить услуги, аналогичные этому примеру.

Сигнализация GotChas

  • RTCPeerConnection не начнет собирать кандидатов, пока не будет вызван setLocalDescription() . Это уполномочено в проекте JSEP IETF .
  • Воспользуйтесь льдом. Позвоните addIceCandidate() , как только прибывают кандидаты.

Готовые сигнальные серверы

Если вы не хотите бросить свои собственные, есть несколько доступных серверов сигнализации WEBRTC, которые используют Socket.io, как предыдущий пример и интегрированы с библиотеками JavaScript клиента WEBRTC:

  • Webrtc.io - одна из первых библиотек абстракций для Webrtc.
  • SignalMaster - это сервер сигнализации, созданный для использования с клиентской библиотекой JavaScript SimpleWebrtc .

Если вы вообще не хотите писать какой -либо код, полные коммерческие платформы WEBRTC доступны в компаниях, таких как Vline , Opentok и Asterisk .

Для записи Ericsson построил сигнальный сервер, используя PHP на Apache в первые дни WEBRTC. Теперь это несколько устарело, но стоит посмотреть на код, если вы рассматриваете что -то подобное.

Сигнальная безопасность

«Безопасность - это искусство, что ничего не случилось».

Салман Рушди

Шифрование является обязательным для всех компонентов WEBRTC.

Тем не менее, механизмы сигнализации не определяются стандартами WEBRTC, поэтому вы должны сделать сигнализацию безопасной. Если злоумышленнику удается угнать сигнализацию, он может остановить сеансы, перенаправлять соединения и записывать, изменять или вводить содержание.

Наиболее важным фактором в обеспечении передачи сигналов является использование безопасных протоколов - HTTPS и WSS (например, TLS) - которые гарантируют, что сообщения не могут быть перехвачены незашифрованными. Кроме того, будьте осторожны, чтобы не транслировать сигнальные сообщения таким образом, чтобы к ним можно было получить доступ других абонентов, использующих тот же сервер сигнализации.

После сигнализации: Используйте лед, чтобы справиться с NAT и брандмауэрами

Для передачи сигналов метаданных приложения WEBRTC используют промежуточный сервер, но для фактической потоковой передачи носителей и данных после установки сеанса RTCPeerConnection пытается напрямую подключить клиентов или одноранговые.

В более простом мире у каждой конечной точки WEBRTC был бы уникальный адрес, который он мог бы обмениваться с другими сверстниками, чтобы общаться напрямую.

Простое соединение со стороны сверстников
Мир без нац и брандмауэров

На самом деле, большинство устройств живут за одним или несколькими слоями NAT , у некоторых есть антивирусное программное обеспечение, которое блокирует определенные порты и протоколы, и многие стоят за прокси и корпоративными брандмауэрами. Брандмауэр и NAT на самом деле могут быть реализованы тем же устройством, таким как домашний маршрутизатор WiFi.

Сверстники позади наса и брандмауэров
Реальный мир

Приложения WEBRTC могут использовать рамку ICE для преодоления сложностей реальной сети. Чтобы это позволило этому произойти, ваше приложение должно передавать URL -адреса Ice Server в RTCPeerConnection , как описано в этой статье.

ICE пытается найти лучший путь для подключения сверстников. Он пробует все возможности параллельно и выбирает наиболее эффективный вариант, который работает. ICE First пытается создать соединение, используя адрес хоста, полученный из операционной системы устройства и сетевой карты. Если это не удастся (что это будет для устройств, стоящих за NAT), ICE получает внешний адрес, используя STUN Server, и, если это не удастся, трафик маршрутизируется через сервер реле Turn.

Другими словами, для получения внешнего сетевого адреса используется сервер STUN, а серверы поворота используются для передачи трафика, если прямое (одноранговое) соединение не удается.

Каждый сервер поворота поддерживает оглушение. Сервер поворота-это STUN Server с дополнительной встроенной функцией ретрансляции. ICE также справляется со сложностями настройки NAT. В действительности, Nat Hole-Changing может потребовать больше, чем просто публичный адрес IP: порт.

URL -адреса для STUN и/или поворотных серверов (необязательно) указаны приложением WEBRTC в объекте конфигурации iceServers , который является первым аргументом для конструктора RTCPeerConnection . Для App.TC это значение выглядит следующим образом:

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

После того, как у RTCPeerConnection есть эта информация, ледяная магия происходит автоматически. RTCPeerConnection использует рамку ICE для проработки лучшего пути между сверстниками, работая с STUN и Turn Servers по мере необходимости.

Оглушающий

NATS предоставляет устройство с IP -адресом для использования в частной локальной сети, но этот адрес не может быть использован снаружи. Без публичного адреса у Webrtc нет возможности общаться. Чтобы обойти эту проблему, WEBRTC использует STUN .

STUN Servers живут в общедоступном Интернете и имеют одну простую задачу - проверьте адрес IP: порт входящего запроса (из приложения, работающего за NAT), и отправьте этот адрес обратно в качестве ответа. Другими словами, приложение использует STUN Server для обнаружения своего IP: порт с публичной точки зрения. Этот процесс позволяет одноранговому коллегу WEBRTC получить общедоступный адрес для себя, а затем передавать его другому однорангу через механизм сигнализации, чтобы настроить прямую ссылку. (На практике разные NAT работают по -разному, и может быть несколько слоев NAT, но принцип все же остается одинаковым.)

Серверы STUN не должны делать много или запомнить много, поэтому относительно низкоспективные STUN-серверы могут обрабатывать большое количество запросов.

Большинство вызовов WEBRTC успешно выполняет соединение, используя STUN - 86% в соответствии с WebrtcStats.com , хотя это может быть меньше для вызовов между сверстниками за брандмауэрами и сложными конфигурациями NAT.

Подключение к одноранговому коллеги
Использование STUN Servers для получения публичных IP -адресов: портовые адреса

ПОВЕРНУТЬ

RTCPeerConnection пытается настроить прямую связь между сверстниками по UDP. Если это не удается, RTCPeerConnection Resorts в TCP. Если это не удастся, поворотные серверы могут использоваться в качестве отрыва, передавать данные между конечными точками.

Просто чтобы повторить, Turn используется для передачи аудио, видео и потоковой передачи данных между коллегами, а не сигналами данных!

Серверы поворотов имеют публичные адреса, поэтому с ними могут связаться со стороны сверстников, даже если сверстники находятся за брандмауэрами или прокси. Повороты серверов имеют концептуально простую задачу - передать поток. Однако, в отличие от оглушенных серверов, они по своей сути потребляют много пропускной способности. Другими словами, поворотные серверы должны быть более сильными.

Подключение к одноранговому коллеги
Полный Монти: оглушить, поворачивать и сигнализировать

Эта диаграмма показывает поворот в действии. Pure Elun не удалось, поэтому каждый одноранговый обращается к использованию сервера поворота.

Развертывание оглушивания и серверов поворота

Для тестирования Google запускает общедоступный STUN Server, STUN.L.Google.com:19302, как это используется App.TC. Для производственного STUN/Turn Service используйте RFC5766-TURN-SERVER. Исходный код для STUN и Turn Servers доступен на GitHub , где вы также можете найти ссылки на несколько источников информации об установке сервера. Изображение виртуальной машины для веб -сервисов Amazon также доступно.

Альтернативный сервер поворота находится RESTUND, доступный в качестве исходного кода , а также для AWS. Вот инструкции по созданию Restund на вычислительном двигателе.

  1. Открытый брандмауэр по мере необходимости для TCP = 443, UDP/TCP = 3478.
  2. Создайте четыре экземпляра, по одному для каждого публичного IP, стандартное изображение Ubuntu 12.06.
  3. Настройка локального конфигурации брандмауэра (разрешайте любой из любого).
  4. Установка инструментов: shell sudo apt-get install make sudo apt-get install gcc
  5. Установите Libre от creytiv.com/re.html .
  6. Fetch Restund от creytiv.com/restund.html и unpack./
  7. wget hancke.name/restund-auth.patch и применить с patch -p1 < restund-auth.patch .
  8. Запустите make , sudo make install для Libre и Restund.
  9. Адаптируйте restund.conf к вашим потребностям (замените IP -адреса и убедитесь, что он содержит тот же общий секрет) и копируйте в /etc . Д.
  10. Скопируйте restund/etc/restund в /etc/init.d/ .
  11. Настройка Restund:
    1. Установить LD_LIBRARY_PATH .
    2. Скопируйте restund.conf в /etc/restund.conf .
    3. Установите restund.conf , чтобы использовать правильный 10. IP -адрес.
  12. Запустить Restund
  13. Тест с использованием клиента Stund от удаленной машины: ./client IP:port

За пределами один к одному: многопартийный WEBRTC

Вы также можете посмотреть на предложенный Джастин Уберти стандарт IETF для API REST для доступа к услугам поворота .

Легко представить варианты использования для потоковой передачи мультимедиа, которые выходят за рамки простого вызова один на один. Например, видеоконференция между группой коллег или публичным событием с одним докладчиком и сотнями или миллионами зрителей.

Приложение WEBRTC может использовать несколько RTCPeerconnections, чтобы каждая конечная точка подключалась к каждой другой конечной точке в конфигурации сетки. Это подход, используемый приложениями, такими как Talky.io , и удивительно хорошо работает для небольшой горстки сверстников. Кроме того, обработка и потребление полосы пропускания становятся чрезмерными, особенно для мобильных клиентов.

Сетка: небольшой звонок
Топология полной сетки: все связаны со всеми

В качестве альтернативы, приложение WEBRTC может выбрать одну конечную точку для распространения потоков для всех остальных в конфигурации звезды. Также можно было бы запустить конечную точку WEBRTC на сервере и построить свой собственный механизм перераспределения ( приложение для примера клиента предоставляется Webrtc.org).

Со времени Chrome 31 и Opera 18, MediaStream от одного RTCPeerConnection может использоваться в качестве ввода для другого. Это может включить более гибкие архитектуры, потому что оно позволяет веб-приложению обрабатывать визит вызовов, выбирая, к какому другому однорангу подключиться. Чтобы увидеть это в действии, см. Выборочные образцы Peer Saels Seerm Samples и выборы WEBRTC несколько сопоставлений .

Многоточечный блок управления

Лучшим вариантом для большого количества конечных точек является использование многоотрального блока управления (MCU). Это сервер, который работает как мост для распространения носителя между большим количеством участников. MCU могут справиться с различными разрешениями, кодеками и частотой кадров на видео конференции; обрабатывать транскодирование; Сделайте селективную переадресацию потока; и микс или записывать аудио и видео. Для многопартийных вызовов есть ряд проблем, которые следует рассмотреть, особенно как отображать несколько видео входов и смешивать аудио из нескольких источников. Облачные платформы, такие как Vline , также пытаются оптимизировать маршрутизацию трафика.

Можно купить полный аппаратный пакет MCU или создать свой собственный.

Вид сзади Cisco MCU5300
Задняя часть Cisco MCU

Доступны несколько вариантов программного обеспечения с открытым исходным кодом. Например, Licode (ранее известный как Lynckia) производит MCU с открытым исходным кодом для WEBRTC. У Opentok есть богомола .

За пределами браузеров: VoIP, телефоны и обмен сообщениями

Стандартизированный характер WEBRTC позволяет установить связь между приложением WEBRTC, работающим в браузере, и устройством или платформой, работающей на другой платформе связи, такой как телефон или система видеоконференции.

SIP -это сигнальный протокол, используемый VoIP и видео-конференциями. Чтобы включить связь между веб-приложением WEBRTC и клиентом SIP, таким как система видео-конференции, WEBRTC нуждается в прокси-сервере для опосредования сигналов. Передача сигналов должна протекать через шлюз, но после установки связи трафик SRTP (видео и аудио) может проходить непосредственно к однократным.

Общедоступная телефонная сеть (PSTN) представляет собой сеть всех «простых старых» аналоговых телефонов. Для вызовов между веб -приложениями и телефонами WEBRTC трафик должен пройти через шлюз PSTN. Аналогичным образом, веб -приложения WEBRTC нуждаются в промежуточном сервере XMPP для связи с такими конечными точками Jingle , как клиенты IM. Jingle был разработан Google как расширение на XMPP, чтобы включить голос и видео для служб обмена сообщениями. Текущие реализации WEBRTC основаны на библиотеке C ++ Libjingle , внедрении Jingle, изначально разработанной для разговора.

Ряд приложений, библиотек и платформ используют способность WEBRTC общаться с внешним миром:

  • SIPML5 : клиент JavaScript с открытым исходным кодом
  • JSSIP : библиотека SIP JavaScript
  • Phono : API Phone API с открытым исходным кодом, созданный как плагин
  • Zingaya : встроенный виджет телефона
  • Twilio : голос и сообщения
  • UberConference : Конференция

Разработчики SIPML5 также построили шлюз WEBRTC2SIP . Tethr и Tropo продемонстрировали основу для связи с стихийными бедствиями «в портфеле» с использованием ячейки OpenBTS для обеспечения связи между функциями телефонов и компьютеров через WEBRTC. Это телефонное общение без перевозчика!

Узнать больше

WEBRTC Codelab предоставляет пошаговые инструкции для создания приложения видео и текстового чата, используя службу сигнализации Socket.io, работающую на узле.

Презентация Google I/O Webrtc от 2013 года с Webrtc Tech Lead, Justin Uberti

Презентация Криса Уилсона SFHTML5 - Введение в приложения WEBRTC

350-страничная книга WEBRTC: API и протоколы RTCWEB веб-сайта HTML5 в реальном времени предоставляют много подробностей о данных и сигнальных путях и включает в себя ряд подробных схем топологии сети.

Webrtc и сигнализация: чему научило нас два года - пост в блоге Tokbox о том, почему выпуск сигналов из спецификации был хорошей идеей

Бен Стронг - это практическое руководство по созданию приложений WEBRTC предоставляет много информации о топологиях и инфраструктуре WEBRTC.

Глава WEBRTC в сетях браузера Ильи Григорика углубляется в архитектуру WEBRTC, варианты использования и производительность.

,

Что такое сигнализация?

Сигнализация - это процесс координации связи. Чтобы приложение WEBRTC мог бы настроить звонок, его клиенты должны обмениваться следующей информацией:

  • Сообщения контроля сеанса, используемые для открытия или закрытия общения
  • Сообщения об ошибках
  • Метаданные мультимедиа, такие как кодеки, настройки кодека, пропускная способность и типы мультимедиа
  • Ключевые данные, используемые для установления безопасных соединений
  • Сетевые данные, такие как IP -адрес хоста и порт, как видно из внешнего мира

Этот процесс сигнализации нуждается в способе для клиентов, чтобы передавать сообщения вперед и назад. Этот механизм не реализуется APIS WEBRTC. Вы должны построить его самостоятельно. Позже в этой статье вы изучаете способы создания сигнальной услуги. Во -первых, вам нужен небольшой контекст.

Почему сигнализация не определена WEBRTC?

Чтобы избежать избыточности и максимизировать совместимость с установленными технологиями, методы сигнализации и протоколы не указаны стандартами WEBRTC. Этот подход изложено протоколом заведения сессии JavaScript (JSEP) :

Архитектура JSEP также позволяет избежать браузера, необходимого сохранять состояние, то есть функционировать в качестве сигнального состояния. Это было бы проблематично, если бы, например, данные сигнализации были потеряны каждый раз, когда страница была перезагружена. Вместо этого состояние сигнализации можно сохранить на сервере.

Архитектурная диаграмма JSEP
Архитектура JSEP

JSEP требует обмена между сверстниками предложения и ответа , упомянутые выше метаданные СМИ. Предложения и ответы сообщаются в формате протокола (SDP), которые выглядят так:

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

Хотите знать, что на самом деле означает весь этот SDP Gobbledygook? Взгляните на примеры интернет -инженерной группы (IETF) .

Имейте в виду, что WEBRTC разработан таким образом, чтобы предложение или ответ можно было настроить, прежде чем быть установленным в качестве локального или удаленного описания, редактируя значения в тексту SDP. Например, функция preferAudioCodec() в app.TC может использоваться для установки кодека по умолчанию и битрейта. SDP несколько болезненна манипулировать JavaScript, и есть обсуждение того, должны ли будущие версии Webrtc использовать JSON, но есть некоторые преимущества для придерживания SDP.

RTCPeerConnection API и сигнализация: предложение, ответ и кандидат

RTCPeerConnection - это API, используемый приложениями WEBRTC для создания соединения между коллегами и передачи аудио и видео.

Чтобы инициализировать этот процесс, у RTCPeerConnection две задачи:

  • Выясняют условия местных средах, такие как разрешение и возможности кодека. Это метаданные, используемые для механизма предложения и ответа.
  • Получите потенциальные сетевые адреса для хоста приложения, известного как кандидаты .

После того, как эти локальные данные будут установлены, их необходимо обмениваться с помощью сигнального механизма с дистанционным сверстником.

Представьте, что Алиса пытается назвать Еву . Вот полный механизм предложения/ответа во всех его кровавых деталях:

  1. Алиса создает объект RTCPeerConnection .
  2. Алиса создает предложение (описание сеанса SDP) с помощью метода RTCPeerConnection createOffer() .
  3. Алиса называет setLocalDescription() с ее предложением.
  4. Алиса строит предложение и использует механизм сигнализации, чтобы отправить его в Еву.
  5. Eve Calls setRemoteDescription() с предложением Алисы, так что ее RTCPeerConnection знала о настройке Алисы.
  6. Ева называет createAnswer() , и обратный вызов успеха для этого передается описание локального сеанса - Ответ Евы.
  7. Ева устанавливает свой ответ в качестве локального описания, позвонив setLocalDescription() .
  8. Затем Ева использует механизм сигнализации, чтобы отправить свой строгий ответ Алисе.
  9. Алиса устанавливает ответ Евы в качестве описания удаленного сеанса с использованием setRemoteDescription() .

Алиса и Ева также должны обмениваться информацией о сети. Выражение «поиск кандидатов» относится к процессу поиска сетевых интерфейсов и портов с использованием структуры льда .

  1. Алиса создает объект RTCPeerConnection с помощью обработчика onicecandidate .
  2. Обработчик называется, когда кандидаты в сеть становятся доступными.
  3. В обработчике Алиса отправляет строгие данные кандидата в Еву через свой канал сигнализации.
  4. Когда Ева получает сообщение кандидата от Алисы, она называет addIceCandidate() чтобы добавить кандидата в отдаленное описание сверстников.

JSEP поддерживает кандидат в кандидат на ICE , что позволяет вызывающему абоненту постепенно предоставлять кандидатам в Callee после первоначального предложения, и для того, чтобы Callee начала действовать на вызове и устанавливает соединение, не ожидая, когда все кандидаты прибудут.

Код webrtc для сигнализации

Следующий фрагмент кода является примером кода W3C , который суммирует полный процесс сигнализации. Код предполагает существование некоторого механизма сигнализации, SignalingChannel . Сигнализация обсуждается более подробно позже.

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

Чтобы увидеть процессы предложения/ответа и обмена кандидата в действии, см. Simpl.info Rtcpeerconnection и посмотрите журнал консоли для одностраничного примера видеочата. Если вы хотите большего, загрузите полный дамп сигнализации и статистики WEBRTC со страницы ae: // webrtc-internals в Google Chrome или на странице Opera: // webrtc-internals в Opera.

Открытие сверстников

Это причудливый способ спросить: «Как мне найти с кем поговорить?»

Для телефонных звонков у вас есть телефонные номера и каталоги. Для онлайн -видеочата и обмена сообщениями вам нужны системы управления идентификацией и присутствием, а также средство для пользователей для начала сессий. Приложения WEBRTC нуждаются в способе для клиентов, чтобы сигнализировать друг другу, что они хотят начать или присоединиться к вызову.

Механизмы обнаружения сверстников не определяются WEBRTC, и вы не попадаете в варианты здесь. Процесс может быть таким же простым, как электронная почта или обмен сообщениями по URL. Для приложений для видеочата, таких как Talky , Tawk.to и собрание браузера , вы приглашаете людей позвонить, поделившись пользовательской ссылкой. Разработчик Крис Болл построил интригующий эксперимент без сервера-Webrtc , который позволяет участникам вызова WEBRTC обменять метаданные с помощью любых услуг обмена сообщениями, которые им нравятся, например, IM, электронная почта или голубь.

Как вы можете создать сигнальную услугу?

Чтобы повторить, сигнальные протоколы и механизмы не определяются стандартами WEBRTC. Что бы вы ни выбрали, вам нужен промежуточный сервер для обмена сообщениями сигнализации и данных приложений между клиентами. К сожалению, веб -приложение не может просто кричать в Интернет: «Подключите меня к своему другу!»

К счастью, сигнальные сообщения маленькие и в основном обмениваются в начале звонка. При тестировании с App.TC для сеанса видеочата в общей сложности около 30-45 сообщений были обработаны службой сигнализации с общим размером для всех сообщений около 10 КБ.

Помимо относительно нетребования с точки зрения полосы пропускания, сигнальные услуги WEBRTC не потребляют много обработки или памяти, потому что им необходимо только передавать сообщения и сохранять небольшой объем данных состояния сеанса, например, какие клиенты подключены.

Протолкнуть сообщения с сервера к клиенту

Служба сообщений для сигнализации должна быть двунаправленной: клиент для сервера и сервер для клиента. Двунаправленная связь идет против модели запроса/ответа клиента/сервера HTTP, но в течение многих лет были разработаны различные взломы, такие как долгие опросы , чтобы натолкнуть данные с службы, работающего на веб -сервере, в веб -приложение, работающее в браузере.

Совсем недавно API EventSource был широко реализован . Это включает в себя серверные события - данные, отправляемые с веб -сервера в клиент браузера через HTTP. EventSource is designed for one-way messaging, but it can be used in combination with XHR to build a service for exchanging signaling messages. A signaling service passes a message from a caller, delivered by XHR request, by pushing it through EventSource to the callee.

WebSocket is a more-natural solution, designed for full duplex client–server communication - messages that can flow in both directions at the same time. One advantage of a signaling service built with pure WebSocket or server-sent events ( EventSource ) is that the backend for these APIs can be implemented on a variety of web frameworks common to most web-hosting packages for languages such as PHP, Python, and Ruby.

All modern browsers except Opera Mini support WebSocket and, more importantly, all browsers that support WebRTC also support WebSocket, both on desktop and mobile. TLS should be used for all connections to ensure messages cannot be intercepted unencrypted and also to reduce problems with proxy traversal . (For more information about WebSocket and proxy traversal see the WebRTC chapter in Ilya Grigorik's High Performance Browser Networking .)

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

Scale signaling

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

  • eXtensible Messaging and Presence Protocol (XMPP), originally known as Jabber-a protocol developed for instant messaging that can be used for signaling (Server implementations include ejabberd and Openfire . JavaScript clients, such as Strophe.js , use BOSH to emulate bidirectional streaming, but for various reasons , BOSH may not be as efficient as WebSocket and, for the same reasons, may not scale well.) (On a tangent, Jingle is an XMPP extension to enable voice and video. The WebRTC project uses network and transport components from the libjingle library - a C++ implementation of Jingle.)

  • Open source libraries, such as ZeroMQ (as used by TokBox for their Rumour service) and OpenMQ ( NullMQ applies ZeroMQ concepts to web platforms using the STOMP protocol over WebSocket.)

  • Commercial cloud-messaging platforms that use WebSocket (though they may fall back to long polling), such as Pusher , Kaazing , and PubNub (PubNub also has an API for WebRTC .)

  • Commercial WebRTC platforms, such as vLine

(Developer Phil Leggetter's Real-Time Web Technologies Guide provides a comprehensive list of messaging services and libraries.)

Build a signaling service with Socket.io on Node

The following is code for a simple web app that uses a signaling service built with Socket.io on Node . The design of Socket.io makes it simple to build a service to exchange messages and Socket.io is particularly suited to WebRTC signaling because of its built-in concept of rooms. This example is not designed to scale as a production-grade signaling service, but is simple to understand for a relatively small number of users.

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

There's no WebRTC in this example. It's designed only to show how to build signaling into a web app. View the console log to see what's happening as clients join a room and exchange messages. This WebRTC codelab gives step-by-step instructions for how to integrate this into a complete WebRTC video chat app.

Here is the client index.html :

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

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

const isInitiator;

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

const socket = io.connect();

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

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

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

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

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

Here's the complete server app:

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

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

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

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

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

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

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

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

  });

});

(You don't need to learn about node-static for this. It just happens to be used in this example.)

To run this app on localhost, you need to have Node, Socket.IO, and node-static installed. Node can be downloaded from Node.js (installation is straightforward and quick). To install Socket.IO and node-static, run Node Package Manager from a terminal in your app directory:

npm install socket.io
npm install node-static

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

node server.js

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

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

Signaling gotchas

  • RTCPeerConnection won't start gathering candidates until setLocalDescription() is called. This is mandated in the JSEP IETF draft .
  • Take advantage of Trickle ICE. Call addIceCandidate() as soon as candidates arrive.

Readymade signaling servers

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

If you don't want to write any code at all, complete commercial WebRTC platforms are available from companies, such as vLine , OpenTok , and Asterisk .

For the record, Ericsson built a signaling server using PHP on Apache in the early days of WebRTC. This is now somewhat obsolete, but it's worth looking at the code if you're considering something similar.

Signaling security

"Security is the art of making nothing happen."

Salman Rushdie

Encryption is mandatory for all WebRTC components.

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

The most important factor in securing signaling is to use secure protocols - HTTPS and WSS (for example, TLS) - which ensure that messages cannot be intercepted unencrypted. Also, be careful not to broadcast signaling messages in a way that they can be accessed by other callers using the same signaling server.

After signaling: Use ICE to cope with NATs and firewalls

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

In a simpler world, every WebRTC endpoint would have a unique address that it could exchange with other peers in order to communicate directly.

Simple peer to peer connection
A world without NATs and firewalls

In reality, most devices live behind one or more layers of NAT , some have antivirus software that blocks certain ports and protocols, and many are behind proxies and corporate firewalls. A firewall and NAT may in fact be implemented by the same device, such as a home WIFI router.

Peers behind NATs and firewalls
The real world

WebRTC apps can use the ICE framework to overcome the complexities of real-world networking. To enable this to happen, your app must pass ICE server URLs to RTCPeerConnection , as described in this article.

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

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

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

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

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

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

Оглушающий

NATs provide a device with an IP address for use within a private local network, but this address can't be used externally. Without a public address, there's no way for WebRTC peers to communicate. To get around this problem, WebRTC uses STUN .

STUN servers live on the public internet and have one simple task - check the IP:port address of an incoming request (from an app running behind a NAT) and send that address back as a response. In other words, the app uses a STUN server to discover its IP:port from a public perspective. This process enables a WebRTC peer to get a publicly accessible address for itself and then pass it to another peer through a signaling mechanism in order to set up a direct link. (In practice, different NATs work in different ways and there may be multiple NAT layers, but the principle is still the same.)

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

Most WebRTC calls successfully make a connection using STUN - 86% according to Webrtcstats.com , though this can be less for calls between peers behind firewalls and complex NAT configurations.

Peer to peer connection using a STUN server
Using STUN servers to get public IP:port addresses

ПОВЕРНУТЬ

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

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

TURN servers have public addresses, so they can be contacted by peers even if the peers are behind firewalls or proxies. TURN servers have a conceptually simple task - to relay a stream. However, unlike STUN servers, they inherently consume a lot of bandwidth. In other words, TURN servers need to be beefier.

Peer to peer connection using a STUN server
The full Monty: STUN, TURN, and signaling

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

Deploying STUN and TURN servers

For testing, Google runs a public STUN server, stun.l.google.com:19302, as used by appr.tc . For a production STUN/TURN service, use the rfc5766-turn-server. Source code for STUN and TURN servers is available on GitHub , where you can also find links to several sources of information about server installation. A VM image for Amazon Web Services is also available.

An alternative TURN server is restund, available as source code and also for AWS. Here are instructions for how to set up restund on Compute Engine.

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

Beyond one-to-one: Multiparty WebRTC

You may also want to take a look at Justin Uberti's proposed IETF standard for a REST API for access to TURN Services .

It's easy to imagine use cases for media streaming that go beyond a simple one-to-one call. For example, video conferencing between a group of colleagues or a public event with one speaker and hundreds or millions of viewers.

A WebRTC app can use multiple RTCPeerConnections so that every endpoint connects to every other endpoint in a mesh configuration. This is the approach taken by apps, such as talky.io , and works remarkably well for a small handful of peers. Beyond that, processing and bandwidth consumption becomes excessive, especially for mobile clients.

Mesh: small N-way call
Full mesh topology: Everyone connected to everyone

Alternatively, a WebRTC app could choose one endpoint to distribute streams to all others in a star configuration. It would also be possible to run a WebRTC endpoint on a server and construct your own redistribution mechanism (a sample client app is provided by webrtc.org).

Since Chrome 31 and Opera 18, a MediaStream from one RTCPeerConnection can be used as the input for another. This can enable more flexible architectures because it enables a web app to handle call-routing by choosing which other peer to connect to. To see this in action, see WebRTC samples Peer connection relay and WebRTC samples Multiple peer connections .

Multipoint Control Unit

A better option for a large number of endpoints is to use a Multipoint Control Unit (MCU). This is a server that works as a bridge to distribute media between a large number of participants. MCUs can cope with different resolutions, codecs, and frame rates in a video conference; handle transcoding; do selective stream forwarding; and mix or record audio and video. For multiparty calls, there are a number of issues to consider, particularly how to display multiple video inputs and mix audio from multiple sources. Cloud platforms, such as vLine , also attempt to optimize traffic routing.

It's possible to buy a complete MCU hardware package or build your own.

Rear view of Cisco MCU5300
The back of a Cisco MCU

Several open source MCU software options are available. For example, Licode (previously known as Lynckia) produces an open source MCU for WebRTC. OpenTok has Mantis .

Beyond browsers: VoIP, telephones, and messaging

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

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

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

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

  • sipML5 : an open source JavaScript SIP client
  • jsSIP : JavaScript SIP library
  • Phono : open source JavaScript phone API built as a plugin
  • Zingaya : an embeddable phone widget
  • Twilio : voice and messaging
  • Uberconference : conferencing

The sipML5 developers have also built the webrtc2sip gateway. Tethr and Tropo have demonstrated a framework for disaster communications "in a briefcase" using an OpenBTS cell to enable communications between feature phones and computers through WebRTC. That's telephone communication without a carrier!

Узнать больше

The WebRTC codelab provides step-by-step instructions for how to build a video and text chat app using a Socket.io signaling service running on Node.

Google I/O WebRTC presentation from 2013 with WebRTC tech lead, Justin Uberti

Chris Wilson's SFHTML5 presentation - Introduction to WebRTC Apps

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

WebRTC and Signaling: What Two Years Has Taught Us - TokBox blog post about why leaving signaling out of the spec was a good idea

Ben Strong's A Practical Guide to Building WebRTC Apps provides a lot of information about WebRTC topologies and infrastructure.

The WebRTC chapter in Ilya Grigorik's High Performance Browser Networking goes deep into WebRTC architecture, use cases, and performance.

,

What is signaling?

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

  • Session-control messages used to open or close communication
  • Сообщения об ошибках
  • Media metadata, such as codecs, codec settings, bandwidth, and media types
  • Key data used to establish secure connections
  • Network data, such as a host's IP address and port as seen by the outside world

This signaling process needs a way for clients to pass messages back and forth. That mechanism is not implemented by the WebRTC APIs. You need to build it yourself. Later in this article, you learn ways to build a signaling service. First, however, you need a little context.

Why is signaling not defined by WebRTC?

To avoid redundancy and to maximize compatibility with established technologies, signaling methods and protocols are not specified by WebRTC standards. This approach is outlined by the JavaScript Session Establishment Protocol (JSEP) :

JSEP's architecture also avoids a browser having to save state, that is, to function as a signaling state machine. This would be problematic if, for example, signaling data was lost each time a page was reloaded. Instead, signaling state can be saved on a server.

JSEP architecture diagram
JSEP architecture

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

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

Want to know what all this SDP gobbledygook actually means? Take a look at the Internet Engineering Task Force (IETF) examples .

Bear in mind that WebRTC is designed so that the offer or answer can be tweaked before being set as the local or remote description by editing the values in the SDP text. For example, the preferAudioCodec() function in appr.tc can be used to set the default codec and bitrate. SDP is somewhat painful to manipulate with JavaScript and there is discussion about whether future versions of WebRTC should use JSON instead, but there are some advantages to sticking with SDP.

RTCPeerConnection API and signaling: Offer, answer, and candidate

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

To initialize this process, RTCPeerConnection has two tasks:

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

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

Imagine Alice is trying to call Eve . Here's the full offer/answer mechanism in all its gory detail:

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

Alice and Eve also need to exchange network information. The expression "finding candidates" refers to the process of finding network interfaces and ports using the ICE framework .

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

JSEP supports ICE Candidate Trickling , which allows the caller to incrementally provide candidates to the callee after the initial offer, and for the callee to begin acting on the call and set up a connection without waiting for all candidates to arrive.

Code WebRTC for signaling

The following code snippet is a W3C code example that summarizes the complete signaling process. The code assumes the existence of some signaling mechanism, SignalingChannel . Signaling is discussed in greater detail later.

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

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

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

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

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

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

To see the offer/answer and candidate-exchange processes in action, see simpl.info RTCPeerConnection and look at the console log for a single-page video chat example. If you want more, download a complete dump of WebRTC signaling and stats from the about://webrtc-internals page in Google Chrome or the opera://webrtc-internals page in Opera.

Peer discovery

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

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

Peer discovery mechanisms are not defined by WebRTC and you don't go into the options here. The process can be as simple as emailing or messaging a URL. For video chat apps, such as Talky , tawk.to and Browser Meeting , you invite people to a call by sharing a custom link. Developer Chris Ball built an intriguing serverless-webrtc experiment that enables WebRTC call participants to exchange metadata by any messaging service they like, such as IM, email, or homing pigeon.

How can you build a signaling service?

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

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

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

Push messages from the server to the client

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

More recently, the EventSource API has been widely implemented . This enables server-sent events - data sent from a web server to a browser client through HTTP. EventSource is designed for one-way messaging, but it can be used in combination with XHR to build a service for exchanging signaling messages. A signaling service passes a message from a caller, delivered by XHR request, by pushing it through EventSource to the callee.

WebSocket is a more-natural solution, designed for full duplex client–server communication - messages that can flow in both directions at the same time. One advantage of a signaling service built with pure WebSocket or server-sent events ( EventSource ) is that the backend for these APIs can be implemented on a variety of web frameworks common to most web-hosting packages for languages such as PHP, Python, and Ruby.

All modern browsers except Opera Mini support WebSocket and, more importantly, all browsers that support WebRTC also support WebSocket, both on desktop and mobile. TLS should be used for all connections to ensure messages cannot be intercepted unencrypted and also to reduce problems with proxy traversal . (For more information about WebSocket and proxy traversal see the WebRTC chapter in Ilya Grigorik's High Performance Browser Networking .)

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

Scale signaling

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

  • eXtensible Messaging and Presence Protocol (XMPP), originally known as Jabber-a protocol developed for instant messaging that can be used for signaling (Server implementations include ejabberd and Openfire . JavaScript clients, such as Strophe.js , use BOSH to emulate bidirectional streaming, but for various reasons , BOSH may not be as efficient as WebSocket and, for the same reasons, may not scale well.) (On a tangent, Jingle is an XMPP extension to enable voice and video. The WebRTC project uses network and transport components from the libjingle library - a C++ implementation of Jingle.)

  • Open source libraries, such as ZeroMQ (as used by TokBox for their Rumour service) and OpenMQ ( NullMQ applies ZeroMQ concepts to web platforms using the STOMP protocol over WebSocket.)

  • Commercial cloud-messaging platforms that use WebSocket (though they may fall back to long polling), such as Pusher , Kaazing , and PubNub (PubNub also has an API for WebRTC .)

  • Commercial WebRTC platforms, such as vLine

(Developer Phil Leggetter's Real-Time Web Technologies Guide provides a comprehensive list of messaging services and libraries.)

Build a signaling service with Socket.io on Node

The following is code for a simple web app that uses a signaling service built with Socket.io on Node . The design of Socket.io makes it simple to build a service to exchange messages and Socket.io is particularly suited to WebRTC signaling because of its built-in concept of rooms. This example is not designed to scale as a production-grade signaling service, but is simple to understand for a relatively small number of users.

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

There's no WebRTC in this example. It's designed only to show how to build signaling into a web app. View the console log to see what's happening as clients join a room and exchange messages. This WebRTC codelab gives step-by-step instructions for how to integrate this into a complete WebRTC video chat app.

Here is the client index.html :

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

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

const isInitiator;

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

const socket = io.connect();

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

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

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

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

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

Here's the complete server app:

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

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

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

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

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

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

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

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

  });

});

(You don't need to learn about node-static for this. It just happens to be used in this example.)

To run this app on localhost, you need to have Node, Socket.IO, and node-static installed. Node can be downloaded from Node.js (installation is straightforward and quick). To install Socket.IO and node-static, run Node Package Manager from a terminal in your app directory:

npm install socket.io
npm install node-static

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

node server.js

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

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

Signaling gotchas

  • RTCPeerConnection won't start gathering candidates until setLocalDescription() is called. This is mandated in the JSEP IETF draft .
  • Take advantage of Trickle ICE. Call addIceCandidate() as soon as candidates arrive.

Readymade signaling servers

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

If you don't want to write any code at all, complete commercial WebRTC platforms are available from companies, such as vLine , OpenTok , and Asterisk .

For the record, Ericsson built a signaling server using PHP on Apache in the early days of WebRTC. This is now somewhat obsolete, but it's worth looking at the code if you're considering something similar.

Signaling security

"Security is the art of making nothing happen."

Salman Rushdie

Encryption is mandatory for all WebRTC components.

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

The most important factor in securing signaling is to use secure protocols - HTTPS and WSS (for example, TLS) - which ensure that messages cannot be intercepted unencrypted. Also, be careful not to broadcast signaling messages in a way that they can be accessed by other callers using the same signaling server.

After signaling: Use ICE to cope with NATs and firewalls

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

In a simpler world, every WebRTC endpoint would have a unique address that it could exchange with other peers in order to communicate directly.

Simple peer to peer connection
A world without NATs and firewalls

In reality, most devices live behind one or more layers of NAT , some have antivirus software that blocks certain ports and protocols, and many are behind proxies and corporate firewalls. A firewall and NAT may in fact be implemented by the same device, such as a home WIFI router.

Peers behind NATs and firewalls
The real world

WebRTC apps can use the ICE framework to overcome the complexities of real-world networking. To enable this to happen, your app must pass ICE server URLs to RTCPeerConnection , as described in this article.

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

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

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

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

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

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

Оглушающий

NATs provide a device with an IP address for use within a private local network, but this address can't be used externally. Without a public address, there's no way for WebRTC peers to communicate. To get around this problem, WebRTC uses STUN .

STUN servers live on the public internet and have one simple task - check the IP:port address of an incoming request (from an app running behind a NAT) and send that address back as a response. In other words, the app uses a STUN server to discover its IP:port from a public perspective. This process enables a WebRTC peer to get a publicly accessible address for itself and then pass it to another peer through a signaling mechanism in order to set up a direct link. (In practice, different NATs work in different ways and there may be multiple NAT layers, but the principle is still the same.)

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

Most WebRTC calls successfully make a connection using STUN - 86% according to Webrtcstats.com , though this can be less for calls between peers behind firewalls and complex NAT configurations.

Peer to peer connection using a STUN server
Using STUN servers to get public IP:port addresses

ПОВЕРНУТЬ

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

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

TURN servers have public addresses, so they can be contacted by peers even if the peers are behind firewalls or proxies. TURN servers have a conceptually simple task - to relay a stream. However, unlike STUN servers, they inherently consume a lot of bandwidth. In other words, TURN servers need to be beefier.

Peer to peer connection using a STUN server
The full Monty: STUN, TURN, and signaling

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

Deploying STUN and TURN servers

For testing, Google runs a public STUN server, stun.l.google.com:19302, as used by appr.tc . For a production STUN/TURN service, use the rfc5766-turn-server. Source code for STUN and TURN servers is available on GitHub , where you can also find links to several sources of information about server installation. A VM image for Amazon Web Services is also available.

An alternative TURN server is restund, available as source code and also for AWS. Here are instructions for how to set up restund on Compute Engine.

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

Beyond one-to-one: Multiparty WebRTC

You may also want to take a look at Justin Uberti's proposed IETF standard for a REST API for access to TURN Services .

It's easy to imagine use cases for media streaming that go beyond a simple one-to-one call. For example, video conferencing between a group of colleagues or a public event with one speaker and hundreds or millions of viewers.

A WebRTC app can use multiple RTCPeerConnections so that every endpoint connects to every other endpoint in a mesh configuration. This is the approach taken by apps, such as talky.io , and works remarkably well for a small handful of peers. Beyond that, processing and bandwidth consumption becomes excessive, especially for mobile clients.

Mesh: small N-way call
Full mesh topology: Everyone connected to everyone

Alternatively, a WebRTC app could choose one endpoint to distribute streams to all others in a star configuration. It would also be possible to run a WebRTC endpoint on a server and construct your own redistribution mechanism (a sample client app is provided by webrtc.org).

Since Chrome 31 and Opera 18, a MediaStream from one RTCPeerConnection can be used as the input for another. This can enable more flexible architectures because it enables a web app to handle call-routing by choosing which other peer to connect to. To see this in action, see WebRTC samples Peer connection relay and WebRTC samples Multiple peer connections .

Multipoint Control Unit

A better option for a large number of endpoints is to use a Multipoint Control Unit (MCU). This is a server that works as a bridge to distribute media between a large number of participants. MCUs can cope with different resolutions, codecs, and frame rates in a video conference; handle transcoding; do selective stream forwarding; and mix or record audio and video. For multiparty calls, there are a number of issues to consider, particularly how to display multiple video inputs and mix audio from multiple sources. Cloud platforms, such as vLine , also attempt to optimize traffic routing.

It's possible to buy a complete MCU hardware package or build your own.

Rear view of Cisco MCU5300
The back of a Cisco MCU

Several open source MCU software options are available. For example, Licode (previously known as Lynckia) produces an open source MCU for WebRTC. OpenTok has Mantis .

Beyond browsers: VoIP, telephones, and messaging

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

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

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

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

  • sipML5 : an open source JavaScript SIP client
  • jsSIP : JavaScript SIP library
  • Phono : open source JavaScript phone API built as a plugin
  • Zingaya : an embeddable phone widget
  • Twilio : voice and messaging
  • Uberconference : conferencing

The sipML5 developers have also built the webrtc2sip gateway. Tethr and Tropo have demonstrated a framework for disaster communications "in a briefcase" using an OpenBTS cell to enable communications between feature phones and computers through WebRTC. That's telephone communication without a carrier!

Узнать больше

The WebRTC codelab provides step-by-step instructions for how to build a video and text chat app using a Socket.io signaling service running on Node.

Google I/O WebRTC presentation from 2013 with WebRTC tech lead, Justin Uberti

Chris Wilson's SFHTML5 presentation - Introduction to WebRTC Apps

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

WebRTC and Signaling: What Two Years Has Taught Us - TokBox blog post about why leaving signaling out of the spec was a good idea

Ben Strong's A Practical Guide to Building WebRTC Apps provides a lot of information about WebRTC topologies and infrastructure.

The WebRTC chapter in Ilya Grigorik's High Performance Browser Networking goes deep into WebRTC architecture, use cases, and performance.