Обзор архитектуры WebRTC - протокол для реализации видеоконференцсвязи

Не секрет, что удаленная работа становится все более популярной с начала эры COVID, и хотя вакцины уже есть, многие компании и команды полностью приняли идею работы в Интернете и не планируют отказываться от нее. В результате растет спрос на инструменты для совместной работы в Интернете; особенно решения для видеоконференцсвязи. Для справки: цена акций Zoom подскочила с 66,64 долларов США в январе 2020 года до 559 долларов США в октябре 2020 года, рост на ~ 838% за 10 месяцев:

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

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

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

Когда я начал разбираться в WebRTC, я понял, что это сложно с архитектурной точки зрения. Чтобы он заработал, мне потребовалось установить несколько приложений. Он не сводится к единой методологии, такой как REST или веб-сокеты, задействовано множество компонентов. Когда я следовал инструкциям, я делал в точности то, что мне было сказано, но быстро понял, что вещи просто не складываются. Как только я попытался внести небольшое изменение в систему, например, добавить возможность звонить более чем двум людям, все начало разваливаться. Зная то, что я знаю сейчас, я пришел к очень важному выводу: вы должны понимать, как WebRTC работает на архитектурном уровне, в большей степени, чем его API.

Итак, в этой статье я хотел бы поговорить о том, как работает WebRTC, сделать обзор задействованных компонентов и того, как они взаимодействуют друг с другом. Я не буду говорить ни о чем конкретном, касающемся его API, так как мне кажется, что по нему есть множество руководств и документов, но есть несколько (хороших) статей о его архитектуре.

Обзор WebRTC

WebRTC - это протокол, который был разработан для обеспечения прямой связи между браузерами. Он включает набор классов и методов для стандартизации процесса и доступен с Chrome 23:

Помимо стандартизации процесса связи, браузеры предоставляют вам простой и безопасный доступ к оборудованию, которое дополняет WebRTC. Вы можете транслировать свой экран, микрофон и камеру; который обычно требует установки внешних подключаемых модулей или двоичных файлов и может быть довольно сложным, учитывая, что для каждой ОС и оборудования требуются разные (и сложные!) конфигурации. Изначально я пытался реализовать совместное использование экрана с помощью ffmpeg; Мне удалось заставить его работать, но столкнулся со многими проблемами совместимости.

Нам действительно повезло, что у нас есть все готово, но мне, вероятно, стоит поговорить о медиа-части в другой статье.

Одноранговые соединения

WebRTC основан на архитектуре p2p (peer to peer); участники звонка несут ответственность за передачу данных с одного конца на другой, не полагаясь на посредников (по большей части я расскажу об этом позже). Если один участник отключится по какой-либо причине, другие будут продолжать передавать данные; в отличие от традиционной связи, при которой данные больше не передаются в потоковом режиме, если соединение с сервером потеряно. Кроме того, одноранговые узлы географически гораздо ближе друг к другу, поэтому данные не должны перемещаться на большое расстояние.

Соответственно, когда я вхожу в беседу, я должен представлять каждого однорангового узла отдельным экземпляром. Таким образом, учитывая, что у меня в беседе всего 4 участника, включая меня, у меня будет 3 экземпляра одноранговых узлов, каждый из которых напрямую связан с другим браузером; вот как будет выглядеть сетка:

Сервер сигнализации

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

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

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

SDP

Как только мы узнаем, что кто-то присоединился к разговору, нам нужно обмениваться информацией о системах друг друга, чтобы установить соединение. Эта информация основана на протоколе, называемом SDP (протокол описания сеанса), и включает сведения о принадлежащем ему одноранговом узле, например какой агент он использует, какое оборудование он поддерживает, какой тип носителя он хотел бы обменивать и т. д. Конфигурация SDP представляет собой простой объект типа "ключ-значение":

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

Однако важно отслеживать, какой конец представляет рассматриваемая конфигурация SDP: нас или другой партнер. При инициализации однорангового экземпляра нам потребуются 2 вещи: локальное описание и удаленное описание. Местное описание представляет нас, а удаленное описание представляет другой конец. Вместе мы сможем успешно установить соединение:

Кандидаты ICE

У однорангового узла может быть много коммуникационных транспортов, а не только один. У кого-то может быть несколько частных IP-адресов / портов и / или несколько общедоступных IP-адресов / портов, и / или различных протоколов, и / или одного или нескольких обратных прокси-серверов и т. Д. Как только мы создадим предложение SDP, WebRTC попытается найти все возможный транспортный обмен данными с браузером, известный как ICE кандидат (интерактивное установление соединения):

Кандидат ICE - это просто еще одна пара ключ-значение, которая должна быть добавлена ​​в SDP. Мы можем либо дождаться, пока WebRTC найдет всех возможных кандидатов и отправить полный SDP, либо мы можем отправить каждого обнаруженного кандидата ICE с сервером сигнализации и постепенно расширить SDP; оба варианта действительны. WebRTC должен знать, как переключаться между ICE и выбирать наиболее подходящий вариант.

По умолчанию WebRTC отдает предпочтение ICE, основанным на UDP (протокол пользовательских дейтаграмм). В отличие от TCP (протокол управления передачей, традиционный протокол, используемый HTTP), где пакеты не передаются в потоковом режиме, если предыдущие пакеты не были отправлены на 100%, UDP будет продолжать потоковую передачу пакетов независимо от состояния предыдущих пакетов, делая общение намного быстрее.

NAT

Сегодня большинство машин не подключены напрямую к глобальной сети и, скорее всего, проходят уровень NAT (преобразование сетевых адресов). Частный IP / порт вашего компьютера будет буквально переведен на другой общедоступный IP / порт при транспортировке через маршрутизатор.

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

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

Ограниченный конусный NAT
Конусный NAT с ограничением - это такой, при котором все запросы с одного и того же внутреннего IP-адреса и порта сопоставляются с одним и тем же внешним IP-адресом и портом. В отличие от NAT с полным конусом, внешний хост (с IP-адресом X) может отправить пакет на внутренний хост только в том случае, если внутренний хост ранее отправил пакет на IP-адрес X.

Конусный NAT с ограничением портов
Конусный NAT с ограничением портов похож на ограниченный конусный NAT, но ограничение включает номера портов. В частности, внешний хост может отправить пакет с исходным IP-адресом X и исходным портом P на внутренний хост только в том случае, если внутренний хост ранее отправил пакет на IP-адрес X и порт P.

Симметричный NAT
Симметричный NAT - это такой, при котором все запросы с одного и того же внутреннего IP-адреса и порта на определенный IP-адрес и порт назначения сопоставляются с одним и тем же внешним IP-адресом и портом. Если один и тот же хост отправляет пакет с тем же адресом источника и портом, но в другое место назначения, используется другое сопоставление. Более того, только внешний хост, который получает пакет, может отправить UDP-пакет обратно на внутренний хост.

Я хотел бы добавить немного к этим определениям. Маршрутизатор будет управлять своим состоянием с помощью таблицы NAT. Таблица будет содержать историю всех своих транзакций; всякий раз, когда мы делаем запрос, будет создана запись и добавлена ​​в таблицу. Запись обычно содержит следующую информацию:

  • Частный IP / порт
  • Публичный IP / порт
  • IP-адрес назначения / порт

Эта информация важна, чтобы мы могли лучше понять будущие принципы.

STUN

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

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

Фактически, все, что он действительно делает, это возвращает публичный IP / порт. Вот что происходит, когда мы пытаемся установить соединение между двумя пирами:

  • Разрешите одноранговому узлу A и узлу B с полным конусным NAT.
  • Узел A получит информацию о своем общедоступном IP / порте с помощью сервера STUN.
  • Одноранговый узел A отправит эту информацию узлу B, используя сервер сигнализации.
  • Узел B получит эту информацию и попытается установить соединение с узлом A.
  • То же самое и наоборот.

Поскольку серверы STUN мало работают, они дешевы; они не требуют аутентификации и часто предлагаются бесплатно.

Если 2 одноранговых узла работают через NAT с полным конусом, общедоступный IP / порт - это все, что им нужно для установления соединения. Маршрутизатор будет смотреть на общедоступный IP / порт, который прикреплен к входящему запросу, и, если он может сопоставить его с частным IP / портом, он примет соединение.

Пробивка отверстий

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

Это создает парадокс. Если есть 2 узла, которые никогда раньше не встречались, как именно они могут установить соединение?

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

  • Пусть одноранговый узел A с ограниченным конусным NAT, а одноранговый узел B с полным конусным NAT.
  • Узел A получит информацию о своем общедоступном IP / порте с помощью сервера STUN.
  • Одноранговый узел A отправит эту информацию узлу B, используя сервер сигнализации.
  • Узел B получит эту информацию и попытается установить соединение с узлом A.
  • Узлу B не удастся установить соединение, но он сохранит общедоступную информацию узла A в своей таблице NAT.
  • Одноранговый узел A попытается установить соединение с одноранговым узлом B. Поскольку одноранговый узел A уже существует в таблице NAT узла B, соединение принимается.
  • Узел A будет хранить общедоступную информацию о узле B.
  • Одноранговый узел B теперь может установить соединение с одноранговым узлом A.

ПЕРЕМЕНА

Когда мы имеем дело с симметричным NAT, мы можем полностью выбросить p2p и прямую связь через браузер в корзину. Давайте сначала понаблюдаем за процессом подключения:

  • Пусть одноранговый узел A с симметричным NAT, а узел B с полным конусным NAT.
  • Узел A получит информацию о своем общедоступном IP / порте с помощью сервера STUN.
  • Одноранговый узел A отправит эту информацию узлу B, используя сервер сигнализации.
  • Узел B получит эту информацию и попытается установить соединение с узлом A.
  • Узлу B не удастся установить соединение, но он сохранит общедоступную информацию узла A в своей таблице NAT.
  • Одноранговый узел A попытается установить соединение с одноранговым узлом B. Однако одноранговый узел B отклонит одноранговый узел A, потому что общедоступная информация, хранящаяся в его таблице NAT, на самом деле отличается от той, которую он фактически получил.

Видите ли, когда общедоступный IP-адрес / порт одного узла не статичен, у нас нет возможности обеспечить прямую связь через браузер. Вот почему WebRTC дает нам возможность указать URL-адрес сервера TURN (обход с использованием реле вокруг NAT).

Обход с использованием реле вокруг NAT (TURN) - это протокол, который помогает в обходе трансляторов сетевых адресов (NAT) или межсетевых экранов для мультимедийных приложений. - Википедия

TURN буквально ходит, чтобы избежать прямого общения. Он использует обратный прокси, и таким образом общедоступный IP / порт остается постоянным, и мы можем установить соединение. Это не только сделает соединение медленнее и менее эффективным, но и сделает его намного дороже. Следовательно, A TURN ICE всегда будет иметь самый низкий приоритет.

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

Круг замкнулся. Теперь 2 или более одноранговых узла могут устанавливать соединение и общаться друг с другом. Другие темы, непосредственно связанные с WebRTC, которые могут вас заинтересовать (не актуальные статьи):

  • Медиа-потоки - как получить данные с камеры и микрофона и передать их своим коллегам с помощью WebRTC.
  • Каналы данных - как вы можете отправлять данные в формате JSON своим сверстникам с помощью WebRTC.
  • Снижение задержки и затрат на транзакции TURN за счет мультирегионального развертывания.

Надеюсь, вам понравилась статья, и рекомендую вам взглянуть на Git Streamer, если вы ищете быстрый и простой способ поделиться своим экраном и кодом одновременно.