В Voximplant мы используем WebRTC с момента ее появления: сначала как альтернативу Flash для голосовых и видеозвонков, а затем как полную замену. Технология прошла долгий и болезненный путь развития, только недавно ее стали поддерживать все основные браузеры, есть сложности с передачей экрана, нескольких видеопотоков, а иногда браузер падает просто если выключить и включить видеопоток. Накопленный опыт позволяет переводить для Хабра интересные статьи, и сегодня мы передаем слово Ли Сильвестру из Xirsys, который расскажет про отладку (видео)звонков в Chrome, Firefox, Safari и Edge. Отлаживать WebRTC непросто, у нас даже есть специальные
инструкции по снятию логов в популярных браузерах. А что есть у Ли – вы узнаете под катом (спойлер: много всего, включая WireShark).
Темная сторона WebRTC
Работая в Xirsys, я видел несколько действительно крутых приложений, которые использовали WebRTC. Но пока небольшая группа разработчиков создает высокотехнологичные штуки, большая часть программистов не могут даже начать использовать WebRTC. Почему? А все просто. Оно сложное.
Многим из нас знакомо типичное веб-приложение. У такого приложения есть клиент, который отправляет запросы и сервер, который на эти запросы отвечает. Простой, линейный и легко предсказуемый процесс. Если что-то пойдет не так, мы обычно знаем где смотреть логи и что могло случиться. А вот с WebRTC все не так просто.
Асинхронность
Если вы когда-нибудь писали многопоточное приложение, то наверняка знаете о той головной боли, которую доставляет подобная разработка. Рейсы, битая память, — но чаще всего просто баги, которые трудно искать.
WebRTC асинхронно по своей природе. И это совсем не простенькая асинхронность AJAX. Если проводить аналогию, то это несколько одновременно запущенных AJAX запросов, которые пытаются согласовать данные на двух компьютерах. То еще развлечение.
Минное поле обхода NAT
Создание веб-приложений сводится к разработке чего-то, что запускается на сервере и отвечает на запросы. Самое страшное что может случиться — это не открытый в IPTables порт. Лечится за 2 минуты. Чего не скажешь о WebRTC.
Веб-серверы, даже не их софт, а железо — это устройства с публичными IP-адресами. Они сделаны, чтобы быть доступными откуда угодно. А WebRTC сделано, чтобы отправлять и принимать данные с компьютеров пользователей. Которые обычно имеют IP-адреса 192.168.что-нибудь и не горят желанием отвечать на сетевые запросы.
Авторы WebRTC об этом знают, поэтому движок будет перебирать разные способы подключения, в попытке установить связь между двумя не очень предназначенными для такого компьютерами.
Где начинать отладку
В этой статье я рассказываю про основной инструментарий для решения самых популярных проблем. Но перед этим давайте посмотрим, как WebRTC обычно устанавливает подключение.
Как WebRTC устанавливает подключение
Все подключения WebRTC требуют «небольшой помощи» со стороны сигнального протокола. «Небольшая помощь» — это ваш собственный сервер и протокол, с помощью которого звонящий сможет пообщаться с тем, кому звонит, прежде чем установить Peer-to-peer подключение.
WebRTC воспользуется сигнальным протоколом для передачи информации об IP-адресах, возможностях по захвату и воспроизведению голоса и видео, топологии сети, передаваемых данных.
Обычно используется протокол COMET (или SIP — примечание переводчика) и веб-сокеты. WebRTC ничем не ограничивает разработчиков, так что можно использовать что нравится, хоть передавать данные через Notepad и копипасту (делали на одном из воркшопов, работает — снова переводчик). Подключенное к обоим компьютерами сигналирование позволяет начать подключение уже по WebRTC.
Offer и answer
WebRTC подключения используют «offer» и «answer»:
- Инициатор подключения создает и передает другой стороне «offer».
- Другая сторона получает «offer», создает «answer» и передает его обратно.
- Инициатор подключения получает «answer».
Это в теории. На практике обмен любезностями выглядит не так просто.
- Перед передачей «offer» инициатор подключения создает экземпляр RTCPeerConnection и получает у него текстовый пакет «SDP» (Session Description Protocol) с помощью rtcPeerConnection.createOffer(); этот пакет описывает возможности по приему/передаче голоса и видео для браузера.
- Содержимое SDP-пакета устанавливается как «описание локальной стороны подключения» с помощью rtcPeerConnection.setLocalDescription().
- Пакет передается другой стороне, где его содержимое устанавливается как «описание другой стороны подключения» с помощью rtcPeerConnection.setRemoteDescription().
- На другой стороне подключения создается собственный SDP-пакет с помощью rtcPeerConnection.createAnswer(), его содержимое устанавливается в качестве «описания локальной стороны подключения».
- Пакет передается инициатору подключения, который устанавливает его содержимое как «описание другой стороны подключения».
И только после всех действий обе подключающиеся стороны знают возможности друг друга по приему и отправке голоса/видео.
ICE-кандидаты
Но одних возможностей по работе с медиаданными мало. Ведь договаривающиеся стороны еще ничего не сказали о состоянии сети.
Узнать, какие видеокодеки поддерживает браузер и есть ли на ноутбуке камера, можно почти мгновенно. Узнать свой внешний IP-адрес и логику работы NAT занимает время, и обмен информацией о состоянии сети происходит по мере получения этой информации.
Благодаря технологии Trickle ICE (поддерживается не всеми браузерами — примечание переводчика) подключение между двумя WebRTC-устройствами может установиться в любой момент – как только будет найден подходящий «кандидат».
Разработчик должен подписаться на событие
onicecandidate (все строчные!) и передавать другой стороне получаемые SDP-пакеты, где их нужно передаваться WebRTC с помощью метода
addIceCandidate (а вот здесь, сюрприз, заглавная буква). Работает в обе стороны.
Подключение
Для установки подключения WebRTC использует такие штуки как STUN (Session Traversal Utilities for NAT) и TURN (Traversal Using Relay around NAT). Звучит страшно, но на самом деле всего лишь два сетевых протокола.
STUN-сервер
Первый из двух протоколов чуть сложнее эхо-сервера. Когда участники подключения хотят описать как к ним подключаться, им нужен их публичный IP-адрес. И скорее всего это не будет IP-адрес компьютера, пользовательским устройствам редко выделяют публичные адреса. Целую технологию NAT придумали, чтобы не выделять. Чтобы все-таки узнать свой публичный адрес, браузер делает запрос к STUN серверу. Проходя через NAT, сетевой пакет меняет свой обратный адрес на публичный. Получив пакет с запросом, STUN-сервер копирует обратный адрес пакета в его payload и отправляет пакет обратно. Проходя через NAT в обратную сторону, пакет теряет публичный IP-адрес, но копия этого адреса остается в payload, где ее может прочитать WebRTC.
TURN-сервер
TURN-сервер использует расширение STUN-протокола. Те же пакеты, заголовки, плюс новая штука:
command. Сервер является прокси: оба клиента подключаются к нему через UDP-порт
allocation и передают через сервер свои данные.
TURN-серверы устроены таким образом, что инициатор подключения имеет больше возможностей, чем другая сторона. Это приводит к интересному эффекту, когда звонок через TURN сервер будет успешным или неуспешным в зависимости от того, кто кому звонит (все вспоминаем скайп — примечание переводчика).
Отладка
Итак, вы дочитали до этого параграфа. Мы с переводчиком рады и помним, что статья об отладке WebRTC. Но все написанное выше — необходимый минимум, без которого можно даже не начинать. А вот если начать, и вы не располагаете нечеловеческим везением, то оно будет ломаться.
Оно будет ломаться множеством разных способов. Первый из них: отсутствие подключения. Вы передали обоим WebRTC настройки STUN и TURN-серверов, помогли им обменяться offer, answer и ICE кандидатами, а видео или голоса нет. С чего начать? С локального воспроизведения проблемы.
Локальная отладка WebRTC
Как я уже писал выше, основная работа WebRTC происходит на стороне браузера. STUN и TURN-серверы невероятно просты, так что большинство проблем случаются в вашем JavaScript-коде, который выполняется в двух браузерах. Sad but true. С другой стороны, если самое интересное происходит локально в браузерах, то у вас есть широкие возможности по отладке!
Первое что надо проверить — это ваше сигналирование. Именно ваш код передает между браузерами конфигурацию аудио с видео (offer, answer) и информацию о сетевых настройках (ice кандидаты). Вам нужно проверить, какие пакеты были отправлены, какие получены и переданы WebRTC:
- другая сторона подключения получила offer? Инициатор подключения получил answer? Подключение не будет установлено без этого минимального обмена любезностями;
- WebRTC на обоих концах подключения передали вам пакеты с ICE-кандидатами? Вы обменялись этими пакетами и передали их обратно противоположной стороне с помощью addIceCandidate?
- если с обменом пакетами все прошло хорошо, был ли вызван обработчик события onaddstream и установили ли вы полученный объект в HTML-элемент для воспроизведения видео (или аудио)?
Если обмен пакетами не вызывает подозрений, то можно углубиться в кишки сессии.
Session Description Protocol
Пакеты Offer, Answer и ICE-кандидатов создаются WebRTC в текстовом формате SDP. На первый взгляд содержимое пакетов выглядит страшновато, но с небольшой подготовкой можно извлечь из них много пользы во время отладки. Wikipedia неплохо описывает SDP, но для вас я нашел
описание получше.
Самое важное поле в SDP пакетах ICE кандидатов это
typ. Для WebRTC поле может иметь одно из трех значений:
- typ host;
- typ srflx;
- typ relay.
typ host
Тип
host задает ICE-кандидат на подключение по локальной сети (WebRTC перебирает несколько кандидатов в надежде установить подключение, заранее неизвестно, какой получится — примечание переводчика). Такое подключение не требует ни STUN, ни TURN-сервера, так как в локальной сети устройства часто могут устанавливать сетевые подключения напрямую. При отладке с локальной сети вам достаточно проверить и отладить передачу
host-пакетов и убедиться, что устройства могут отсылать друг другу UDP-пакеты. Хотя бывают исключения, на практике я видел конфигурации сети, при которых браузеру требовался TURN-сервер, чтобы подключиться… к себе.
typ srflx
Комбинация букв «srflx» расшифровывается как «Server Reflexive» и маркирует кандидатов на подключение с использованием внешнего IP-адреса, где для подключения достаточно STUN-сервера (при этом используется технология NAT penetration, которая успешна примерно в 80% случаев — примечание переводчика).
typ relay
«Relay» маркирует подключение через TURN-сервер, которое почти всегда успешно. Важно помнить, что WebRTC не обязано создать ровно три разных пакета с полем «typ»; как именно выбираются кандидаты – зависит от имплементации WebRTC в конкретной версии браузера.
Тестирование подключения к устройству
Google предлагает специальное
веб-приложение для тестирования WebRTC-подключений на вашем устройстве. Откройте страницу, нажмите кнопку «start» и JavaScript код попробует установить подключение к серверу Google, используя сигналирование, STUN и TURN-серверы Google.
WebRTC Internals
Вы осмотрели все пакеты, проверили код, все выглядит правильно, но не работает? Для таких случаев Google снабдила свой браузер Chrome специальным разделом, который показывает внутренности WebRTC во время установки подключения и немного красивых графиков в случае успешного подключения. Чтобы воспользоваться, откройте в браузере специальную техническую ссылку:
chrome://webrtc-internals
Если у вас уже открыто приложение, использующее WebRTC, то вы сразу увидите кучу технических данных. В противном случае просто откройте еще одну вкладку и в ней что-нибудь, что использует WebRTC. Вкладка отображает все вызовы к объекту
RTCPeerConnection и позволяет увидеть в реальном времени как устанавливается подключением.
Настройка ICE
Сверху страницы отображается ICE-строка, которая была использована при инициализации подключения. Если при ее формировании была допущена ошибка, это сразу же будет видно (под «ICE-строкой» автор имеет в виду конфигурацию объекта RTCPeerConnection со списком STUN и TURN-серверов (объект 'iceServers') – примечание переводчика). Возможно, там нет списка серверов? Вы должны сконфигурировать объект RTCPeerConnection до того, как сделали первый вызов
createOffer или
createAnswer.
События RTCPeerConnection
Следующий раздел internals показывает вызовы методов
RTCPeerConnection и полученные от объекта события в хронологическом порядке. Ошибки заботливо подсвечиваются красным. Обратите внимание, что подсвеченное красным
addIceCandidateFailed часто не является признаком ошибки и подключение может нормально установиться. В случае успешного подключения последним в списке будет событие
iceconnectionstatechange с значением
complete.
Раздел 'stats'
Следующий раздел актуален, когда подключение успешно установлено. Он содержит статистику передаваемых данных и сетевые задержки. Два наиболее интересных параметра:
ssrc и
bweforvideo.
- ssrc, «Stream Source», маркирует каждый ваш звуковой и видеотрэк. Отображает статистику передаваемых данных и такие параметры как round trip time;
- bweforvideo, BandWidth Estimate, отображает ширину используемого сетевого канала.
Функция getStats
Часто вы не сможете получить доступ к странице internals. Например, когда проблема случается у вашего пользователя. В таком случае вы можете получить те же данные, что показывает страница internals, вызывая метод
getStats у объекта
RTCPeerConnection. Этот метод устанавливает функцию обратного вызова, которая будет вызываться WebRTC каждый раз, когда происходит что-нибудь интересное. Вызываемая функция получает объект с теми полями, которые отображает страница internals:
rtcPeerConnection.getStats(function(stats) {
document.getElementById("lostpackets").innerText = stats.packetsLost;
});
Еще один полезный инструмент – это событие
oniceconnectionstatechange объекта
RTCPeerConnection. Обработчик события будет получать информацию о прогрессе подключения. Возможные варианты:
- new: WebRTC ожидает кандидатов от второй стороны подключения, которых нужно добавлять с помощью метода addIceCandidate;
- checking: WebRTC получил кандидатов от второй стороны подключения, сравнивает их с локальными и перебирает варианты;
- connected: выбрана подходящая пара кандидатов и подключение установлено. Примечательно, что после этого кандидаты могут продолжить приходить, в соответствии с протоколом «Trickle ICE»;
- completed: все кандидаты получены и подключение установлено.
- disconnected: подключение разорвано. На нестабильных каналах WebRTC способно само восстановить подключение, следим за флагом connected;
- closed: подключение разорвано и WebRTC больше с ним не работает.
Если подключение закончилось в состоянии
failed, то мы можем осмотреть полученные на обоих сторонах кандидаты и понять, почему не удалось подключиться. Например, если одна сторона предоставила кандидаты
host и
srflx, вторая сторона
host и
relay, но устройства находились в разных сетях.
Черный прямоугольник вместо видео
Часто случается ситуация, когда подключение установилось, звук передается, а вот вместо видео у одного или обоих участников черный прямоугольник. Чаще всего это случается, если назначить полученный объект с видео HTML-элементу до того, как соединение перейдет в состояние
completed.
Как потыкать палочкой снаружи
Кроме самого объекта
RTCPeerConnection и отображаемых браузером internals вы можете использовать инструменты анализа сетевых пакетов, такие как Wireshark. Эти инструменты умеют отображать пакеты используемых WebRTC-протоколов. Например, Wireshark покажет вам содержимое STUN-пакетов в главном окне, и их можно отфильтровать, набрав в поле фильтра ключевое слово «stun»:
На что смотреть в ответах серверов? Если вы видите только ответы с типом
Binding, это значит поддержку только STUN (рассказ о внешнем IP), и WebRTC сможет предложить только
srflx-кандидатов. Если же в ответах есть специфичные для TURN пакеты
Allocation и
CreatePermission, то у WebRTC будет возможность попробовать подключиться через прокси-сервер. Анализатор пакетов помечает успешные и неуспешные
Allocation. Если нет ни одного успешного, то скорее всего переданы неверные параметры доступа к TURN-серверам (которые почти всегда защищают логином и паролем — примечание переводчика).
Если в логе есть пакет
CreatePermission Success Response, то можно предположить, что с конфигурацией STUN и TURN все хорошо. А если еще и
ChannelBind пакет присутствует, то значит удалось установить подключение к TURN-серверу на высокой скорости.
Проблемы сотовой связи
В моей практике множество WebRTC-решений, устанавливающих подключение по WiFi, не могут подключиться по 3G/4G. Запущенное на мобильном устройстве приложение тяжелее отлаживать: у нас нет такого простого анализатора пакетов как Wireshark, а Safari не умеет показывать WebRTC internals. Логика подсказывает, что если приложение нормально работает по WiFi, то проблема не в самом приложении, а в сотовой связи. Как отладить? Взять ноутбук и подключить к нему
3G донгл. Так у вас появляются анализатор пакетов и удобные логи, с помощью которых можно за разумное время найти корень всех бед.
Выводы
Отлаживать WebRTC непросто, но если хорошо поискать в интернете, то можно найти немало статей и примеров. Если вы работаете в области realtime communications, то я рекомендую вам прочитать RFC спецификации на протоколы
STUN,
TURN и технологию
WebRTC. Документы большие, но содержащаяся в них информация помогает делать надежные решения и отвечать на вопрос «почему оно не звонит».