Спойлер: никак. За них это делает разработчик.

Когда много лет назад начали убивать Flash, пострадали не только браузерные игры. Flash традиционно была сильна в голосовых и видеозвонках: прямой доступ к микрофону, камере, динамикам, возможность работать с UDP-пакетами. В HTML5 заменой стала технология WebRTC. Та самая, которая несколько месяцев назад наконец-то приземлилась в Safari и Edge. Теперь можно звонить с веб-страницы, открытой на iPhone, на другую веб-страницу, например, открытую в Firefox Quantum на линуксе.

Одна из «фишек» WebRTC, которой не было у Flash — это возможность P2P-соединений между браузерами. Но чтобы peer-to-peer работал, программисту придется помучиться. О том, как браузеры договариваются куда слать UDP-пакеты, и что при этом должен сделать разработчик — под катом.

«Сигнализация» — то, о чем стараются не говорить


Большинство тюториалов по WebRTC — это рассказ про крутую замену Flash, голосовые и видеозвонки из браузеров, красивая история про peer-to-peer и десятимегабитный видеопоток без задержек при видеозвонке с вашего iPhone на Windows-ноутбук, при условии что они подключены к одному WiFi. В качестве же кода обычно показывают несколько строк JavaScript, убедительно демонстрирующих как все просто.

Фокус в том, что обычно демонстрируется обертка над WebRTC. И кроме сокрытия от разработчика кишок RTCPeerConnection и MediaDevices.getUserMedia, такие обертки прячут от разработчика все коммуникации между двумя браузерами, используя для этого собственное облако и стек технологий: будь это PubNub, Twilio или наш Voximplant. Делать работу за разработчика — хорошо и правильно. Но упрощая стек технологий, мы часто подкладываем себе мину замедленного действия, когда непонимание происходящих «под капотом» процессов приводит к срыву сроков, работающих через раз решениях и «техническим проблемам», о которых так любит с придыханием рассказывать техподдержка.


Этот рассказ — про сигнализацию в WebRTC, как ее делаем мы и другие компании, а также как ее можете сделать вы, если захотите создать свое решение «с нуля» и без использования готовых сервисов.

Зачем нужен сервер при P2P-звонке


Слыша словосочетание «peer-to-peer», мы обычно вспоминаем торренты. У которых вроде как центрального сервера нет. Что такое «сигнализация» в WebRTC и где у нее сервер?

Предположим, вы сделали веб-страницу с WebRTC и JavaScript-кодом. Открыли ее на трех ноутбуках, подключенных к вашему WiFi и хотите, чтобы первый ноутбук сделал видеозвонок на третий. Как WebRTC на первом ноутбуке узнает, что нужно подключаться именно к третьему? Как бы мы поступили на месте разработчиков WebRTC?

Первый пришедший в голову способ — это передать WebRTC первого ноутбука IP-адрес третьего ноутбука и пусть отсылает UDP-пакеты. Но такой способ будет работать, только если оба устройства подключены к одной сети и эта сеть позволяет им принимать пакеты друг от друга (сюрприз — публичный WiFi в отелях и на площадках конференций чаще всего не позволяет). А что если у нас не одна, а три точки доступа WiFi? И все три ноутбука подключены к разным точкам доступа и имеют один и тот же виртуальный IP-адрес, например «192.168.0.5». Куда браузеру, запущенному на первом ноутбуке, отправлять пакеты?

Можно предположить, что в такой ситуации звонка не будет, и нам в любом случае потребуется внешний сервер с «настоящим» IP-адресом, через который браузеры на обоих ноутбуках смогут общаться друг с другом. Но авторы WebRTC посчитали, что голос и видео — это трафикоемкие коммуникации, и если миллионы пользователей Skype for Web или Google Hangouts будут звонить через публичные сервера, то эти сервера лопнут. Создатели WebRTC наделили технологию возможностью «пробивать» NAT и устанавливать P2P-подключения, даже если оба устройства имеют виртуальные IP-адреса и не могут напрямую обмениваться пакетами. Расплатой стала та самая «сигнализация». Разработчик не может просто передать WebRTC IP-адрес второго устройства или внешнего сервера. Ему нужно помочь обоим браузерам внимательно осмотреть сеть и договориться друг с другом. И для этого ему нужен свой Signaling Server.

Offer, Answer, ICE кандидаты и другие страшные слова


Итак, как выглядит видеозвонок между двумя браузерами с точки зрения разработчика?

  1. После всей предварительной подготовки и создании необходимых JavaScript-объектов на первом браузере вызывается WebRTC метод createOffer(), который возвращает текстовый пакет в формате SDP (или, в будущем, JSON-сериализуемый объект, если oRTC версия API заборет «классическую»). Этот пакет содержит информацию о том, что за коммуникации хочет разработчик: голос, видео или отсылать данные, какие кодеки есть — вся вот эта история
  2. А вот теперь — сигнализация. Разработчик должен каким-то способом (really, в спецификации так и написано!) передать этот текстовый пакет offer второму браузеру. Например, используя собственный сервер в интернет и WebSocket-подключение от обоих браузеров
  3. Получив offer на втором браузере, разработчик передает его WebRTC с помощью метода setRemoteDescription(). Затем вызывает метод createAnswer(), который возвращает такой же текстовый пакет в формате SDP, но уже для второго браузера и с учетом полученного пакета от первого
  4. Сигнализация продолжается: разработчик передает текстовый пакет answer обратно первому браузеру
  5. Получив answer на первом браузере, разработчик передает его WebRTC с помощью уже упомянутого метода setRemoteDescription(), после чего WebRTC в обоих браузерах минимально осведомлены друг о друге. Можно подключаться? Увы, нет. На самом деле все только начинается
  6. WebRTC в обоих браузерах начинает изучать состояние сетевого подключения (на самом деле в стандарте не указано когда это нужно делать, и для многих браузеров WebRTC начинает изучать сеть сразу же после создания соответствующих объектов, чтобы не создавать потом лишних задержек при подключении). Когда разработчик на первом шаге создавал объекты WebRTC, он должен был как минимум передать адрес STUN-сервера. Это сервер, который в ответ на UDP-пакет «какой у меня IP» передает IP-адрес, с которого получил этот пакет. WebRTC использует STUN-сервера чтобы получить «внешний» IP-адрес, сравнить его с «внутренним» и понять есть ли NAT. И если есть, то какие обратные порты NAT использует для маршрутизации UDP-пакетов
  7. Время от времени WebRTC на обоих браузерах будет вызывать коллбэк onicecandidate, передавая уже знакомый SIP-пакет с информацией для второго участника подключения. В этом пакете содержится информация о внутреннем и внешнем IP-адресах, попытках подключения, портах используемых NAT и так далее. Разработчик использует сигнализацию, чтобы передавать эти пакеты между браузерами. Переданный пакет отдается WebRTC с помощью метода addIceCandidate()
  8. Через некоторое время WebRTC установит подключение peer-to-peer. Или не сможет, если NAT будет мешать. Для таких случаев разработчик может передать адрес TURN-сервера, который будет использоваться в качестве внешнего соединительного элемента: оба браузера будут передавать через него UDP-пакеты с голосом или видео. Если STUN-сервер можно найти бесплатный (например, есть у google), то TURN-сервер придется поднимать самому. Никому не интересно пропускать через себя терабайты видеотрафика просто так

Все эти нюансы можно скрыть, если воспользоваться готовой платформой. Наш Web SDK правильно настраивает WebRTC, патчит SDP-пакеты, поддерживает WebSocket-подключение к облаку Voximplant и заботится еще о множестве деталей. И конечно же у нас есть собственные STUN- и TURN-сервера, чтобы подключение состоялось в любом случае. Но можно не скрывать нюансы и сделать самому! Доступные в браузерах API сейчас позволяют сделать сигнализацию разными способами, о них — ниже.

Простая сигнализация HTTP-запросами, которая не работает


Первое, что приходит в голову — это простейший HTTP-сервер и xmlHttpRequest/fetch со стороны браузера. Увы, работать будет только для «hello world» из учебника. В реальной жизни сервер ляжет от такого количества запросов. Которые придется делать довольно часто, чтобы нажав «connect» пользователь не ждал несколько минут «установки соединения». И еще их придется делать часто потому, что WebRTC — это realtime история, и offer/answer/ice нужно передавать очень быстро. Задержка даже в несколько секунд может послужить сигналом для WebRTC что «ничего не получается», после чего движок прекратит попытки установить подключение. Как вариант можно попробовать технику «long polling», но на практике она не очень хорошо работает и промежуточная интернет-инфраструктура любит обрывать такие «медленные» HTTP-запросы.


WebSockets-сигнализация: most effective tactics available


Большинство решений, использующих WebRTC, для сигнализации используют WebSockets. Протокол уже достаточно «старый», чтобы его поддерживало подавляющее большинство используемых веб-браузеров и сетевого оборудования. А если использовать обертку вроде socket.io или SocketJS, то в тех редких случаях, когда WebSocket не работает, можно деградировать до HTTP long polling, который будет работать «хоть как-то». Со стороны сервера WebSockets-подключение, по которому не передаются данные, почти не потребляет ресурсов, и сервер может спокойно обслуживать десятки тысяч ожидающих звонка веб-страниц.

Какие проблемы могут быть с WebSockets? Ну, подключения иногда обрываются — это надо обрабатывать. Еще у них высокие таймауты keep alive — подключение может выглядеть живым, но на самом деле оно уже оборвано где-то на промежуточном оборудовании. А мы об этом узнаем только когда не придет очередной keep alive пакет, а это может быть десять минут. В течении которых до нас пытаются дозвониться, но не могут. Этот механизм отдан на откуп реализациям браузеров и серверов, так что ping-pong frame со стороны сервера небесполезно будет проверить и подкрутить в случае необходимости.

HTTP/2-сигнализация как современный аналог WebSocket


Когда 2-я версия HTTP станет более популярной, WebSockets и Server Side Events скорее всего уйдут в прошлое. Бинарный канал общения с сервером в обе стороны, по которому можно получить и HTML-страницу, и картинки, и организовать WebRTC сигнализацию — это очень круто. К сожалению, несмотря на поддержку последними версиями популярных браузеров, HTTP/2 все еще опасно использовать для проектов с широкой аудиторией. Причина — в промежуточном оборудовании, составляющим «скелет» интернета. Все эти роутеры, шлюзы, баррикадки и киски двадцатилетней давности часто завершают HTTP/2-соединения, не понимая что это такое и пытаясь «защитить» что-то от кого-то.

WebRTC-сигнализация как пример рекурсии


А еще для сигнализации WebRTC можно использовать другое подключение WebRTC! Звучит странно, но у этого способа есть свои плюсы. Если первое WebRTC-подключение установить между браузером и облаком (как это делается у нас для не P2P-звонков) с какой-нибудь другой сигнализацией, то у такого подключения затем можно использовать Data Channel API. Которое выгодно отличается от WebSockets тем, что может работать не только «как TCP», но и «как UDP», очень быстро отправляя пакеты без гарантированной доставки. Такой способ позволит очень быстро сигнализировать подключения — быстрее, чем WebSockets и HTTP/2. В ряде случаев такой способ это то, что нужно. Например, в играх.

TL;DL


Резюмируя все описанное: перед тем, как WebRTC установит подключение peer-to-peer, разработчик должен обеспечить возможность двум браузерам (или другим устройствам; библиотека libwebrtc от Google позволяет использовать WebRTC на всем, что движется компилирует C++) обменяться несколькими текстовыми пакетами. Делать это надо быстро, иначе таймауты и ничего не получится. Платформы делают сигнализацию (и многое другое) за разработчика, но если очень надо, то можно сделать самому. Только помнить о куче нюансов, а потом все отладить.

Иллюстрация до ката с сайта www.elasticrtc.com
Иллюстрация дракона с сайта www.sococo.com/blog/webrtc-signaling-here-be-dragons

Комментарии (16)


  1. aylarov
    18.12.2017 16:01
    +1

    Справедливости ради, во Flash был P2P с помощью RTMFP :) но теперь это уже не так важно


    1. Trumanbaz
      18.12.2017 19:30
      +1

      Более того, уже в 2008 был Adobe Cirrus (https://labs.adobe.com/technologies/cirrus/) — сервер, располагающийся у Adobe и выполняющий роль «соединителя» между звонящими друг другу ноутбуками из статьи. Нужно было всего лишь подключиться к Cirrus со своими ID и ключом (аналог ключа разработчика Google Play, только бесплатно), и можно начинать P2P-соединения.


  1. yu5k3
    18.12.2017 16:48

    патчит SDP-пакеты

    А можно об этом поподробней? Что имеется ввиду под словом «патчим», вы же формируете SDP-пакеты, разве нет? Или вы используете какой-то особый формат или особые параметры?


    1. eyeofhell Автор
      18.12.2017 17:27

      Формирует WebRTC в браузере. И делает это не всегда правильно. Чтобы все работало кроссплатформенно между Chrome-Firefox-Safari-Edge нужно в этих пакетах еще ручками копаться, увы. Но это тема для отдлеьной хабрастатьи :)


      1. mrlika
        19.12.2017 11:07
        +1

        А можете ткнуть пальцем где почитать? Как раз P2P assisted streaming сделали на RTCDataChannel (привет Edge), а Firefox c Safari не коннектится…


        1. eyeofhell Автор
          19.12.2017 11:09

          У нас несколько инженеров это все вручную изучает :( В Edge, если что, нет DataChannel :) Если не коннектиться, то в первую очередь надо идти в «webrtc-internals» и смотреть что проиходит на уровне offer, answer и кандидатов. Если непонятно — подписаться на все эвенты (там есть эвенты «ошибка» и так далее) и смотреть что WebRTC говорит по поводу подключения.


  1. HexArt
    18.12.2017 17:59
    +1

    Про WebRTC информации очень много, но она рассчитана или на тех, кто с ним еще не работал, или очень мутная. Спасают только примеры от Google и исходники.
    Из статьи можно понять, что для работы вам понадобиться два сервера (сигнальный + TURN). На этом полезная информация заканчивается… Протокол обмена offer, answer и candidates описаны в туториалах довольно подробно.

    При этом не освященных нюансов очень много.
    Например сложно найти информацию о том, что можно во время установленного соединения сгенерировать еще один offer для других MediaDevices и все будет ок. Или о том, что WebRTC под native iOS (еще не проверял на других платформах) позволяет создать виртуальную камеру и перенаправлять в нее потоки от задней и фронтальной без каких-либо проблем.

    Жестко не хватает good practice по работе с WebRTC. Мы например пришли к StateMachine как на сервере, так и на клиентах. Она нас спасла от кучи проблем с коллизиями при встречных звонках.

    Я последний год очень плотно работаю с WebRTC под iOS и если есть вопросы, то могу помочь. ?Было бы круто, если кто-то сможет поделиться инфой по вопросам:
    1. ?Как быстро восстановить соединения при смене сети пользователем? Например телефон перешел из WiFi в сотовую сеть.
    2. Как подружить RTCAudioSession, AVAudioSession и CallKit для воспроизведения сигнальных звуков.


    1. eyeofhell Автор
      19.12.2017 11:11

      1. Хорошего способа неизвестно, только reconnect и хорошая эвристика когда его делать.
      2. Приходил лид iOS разработки, сказал что долго рассказывать, но все гуглится и в целом есть в документации по iOS.


  1. sena
    18.12.2017 19:02

    А зачем вот это вот всё засунули в браузер? Сделали открытый стандарт и хорошо, пусть люди пишут свои клиенты/плагины и т.п. Можно было бы и библиотеку для всех сделать открытую, чтобы легче было поддержку добавить.


    1. aylarov
      18.12.2017 19:16
      +1

      Шел 2017 год, здравых людей, ставящих неизвестные плагины, осталось исчезающей мало


      1. sena
        18.12.2017 20:03

        Я не имел в виду плагины к браузеру, а плагины к существующим коммуникаторам. То есть к приложениям, которые давно этим занимаются и умеют коммуникации гораздо лучше браузера. Зачем это тащить в браузер?


        1. BigD
          18.12.2017 23:53

          Потому что скоро останется только одна программа — браузер.


        1. mogaika
          19.12.2017 06:17

          Никто вам не запрещает использовать плагины для/или определенных приложений.
          Конечно раздражает оверхед на ресурсы, но меня лично радует когда я знаю что определенная вещь заработает из коробки почти по одному клику + на телефоне/компе/телевизоре/планшете. И если помнить что типичный пользователь не отличает вайфай от интернета и логин от пароля, то ему это точно понравится (даже не узнает)…


        1. wataru
          19.12.2017 14:14

          Ну, как бы, это в другую сторону работает. WebRTC — опенсорс и его встраивают во всякие мессенджеры, а не наоборот.


  1. chimvl
    22.12.2017 14:58

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


    1. eyeofhell Автор
      22.12.2017 14:58

      Да, должен.