ICE, STUN и настройка TURN
Offer и answer сообщают браузерам параметры сеанса, но не выбирают автоматически пригодный путь через любые сети. В этом уроке рассмотрим ICE candidates, роль STUN и необходимость TURN. «Разговор» начнёт передавать кандидатов через собственный signaling и получать конфигурацию ICE от локального сервера. Мы не будем считать единственный успешный прямой маршрут доказательством достаточности STUN для всех пользователей.
Состояние lesson-06 добавляет onicecandidate, приём candidate и endpoint /ice. Конфигурация задаётся явным ICE_SERVERS_JSON в окружении сервера. Пустой массив оставляет только локальные host candidates и пригоден для ограниченного локального упражнения. В архиве нет стороннего публичного TURN, скрытого username или встроенного пароля: параметры вашего сервера читатель задаёт самостоятельно.
Кандидаты и проверки пути
ICE собирает возможные адреса и выполняет проверки пар кандидатур между браузерами. Host candidate описывает локально доступный путь, server-reflexive возникает с помощью STUN, relay использует TURN. Эти виды не являются оценками качества камеры. Они относятся к достижимости сетевого маршрута, а успешный захват микрофона не говорит, какой из маршрутов окажется доступным.
STUN помогает выяснить сведения о внешнем адресе, но не гарантирует обход любой NAT или корпоративной политики. TURN предоставляет ретрансляцию медиа, когда пригодного прямого пути нет. Ретранслятор имеет собственную стоимость, пропускную способность, доступность и политику credentials. Для сервиса консультаций нужно предусмотреть такой путь явно, а не только добавить популярный STUN-адрес и объявить сетевую задачу решённой.
Ниже показан точный фрагмент приёма кандидатов из src/app.js. pc, ignoreOffer и очередь state.candidates относятся к текущему epoch. Обработчик description после успешного setRemoteDescription применяет отложенную очередь. Сервер проверяет форму кандидата, подставляет отправителя и пересылает пакет только другому разрешённому участнику.
}else if(m.type==='candidate' && LESSON>=6){
if(ignoreOffer)return;
if(pc.remoteDescription)await pc.addIceCandidate(m.candidate);else state.candidates.push(m.candidate);
}
Кандидат может прийти раньше
Сигнализация состоит из нескольких асинхронных событий. Кандидат способен прийти до того, как локальная сторона завершила применение remote description. Немедленный addIceCandidate в неподходящем состоянии может отказать. Поэтому в контроллере кандидат сохраняется до наличия remote description, а затем применяется в порядке очереди. Это буферизация текущей попытки, а не долговременное хранение всех кандидатов комнаты.
При смене peer или epoch очередь очищается. Старый адрес из предыдущего ICE-поколения не нужно применять к новой паре только потому, что его JSON выглядит корректно. В более сложном протоколе полезно учитывать идентификаторы ICE-поколений и username fragment. Наш пример связывает сообщения с серверным epoch и переговорным состоянием, сохраняя ограниченную модель двух участников.
Пустой кандидат и null имеют специальные значения окончания сбора, поэтому серверная проверка не должна отвергать их как обычный отсутствующий адрес. В то же время произвольный объект из браузера нельзя пересылать без проверки. В коде ожидается известная форма RTCIceCandidateInit, которую клиент получает через toJSON у браузерного кандидата. Мы не составляем строку адреса вручную.
Конфигурация TURN
Endpoint /ice доступен только действующему участнику и возвращает явный массив серверов. Элемент TURN должен иметь urls, username и credential; если параметры неполны, локальный сервер отказывается стартовать. Это предотвращает случайное заявление «TURN включён» при наличии одной строки адреса без допуска. Реальные значения не следует сохранять в статье, общем архиве или публичном клиентском конфигурационном файле.
Браузеру потребуются credentials для подключения к TURN, поэтому полностью спрятать их от разрешённого клиента невозможно. Обычно сервер выдаёт короткоживущие значения с ограниченной областью действия и сроком. Наш учебный endpoint читает уже подготовленные параметры из окружения; он не реализует полноценное динамическое создание TURN-credentials. Это эксплуатационная граница, которую нельзя скрывать за успешным JSON-ответом.
Выбор UDP, TCP и TLS-URL зависит от вашего TURN и поддерживаемых сетей. Не добавляйте вымышленные домены как будто они предоставляют работающее оборудование. При самостоятельной настройке нужно проверить фактическое разрешение имён, сертификат, порты и ресурсные ограничения. Статья описывает контракт конфигурации, но не содержит результатов подключения к реальному ретранслятору.
Ожидаемое наблюдение
После самостоятельной настройки и запуска браузеры должны отправлять собранные candidates через signaling и проверять доступные пары. При пригодном маршруте connection state может перейти в connected. Однако код не гарантирует такой переход при любых правилах сети. Здесь нет снимков сетевой диагностики, фактических IP-адресов или выдуманного времени установления: при авторстве соединение не создавалось.
Для отдельного наблюдения TURN можно временно выбрать iceTransportPolicy: 'relay' при создании peer. Тогда прямые кандидаты не должны подменить проверку ретрансляции. Делать такой вывод по одному успешному обычному соединению нельзя: браузер мог использовать host или server-reflexive маршрут. Поздний урок getStats покажет, как осторожно определить вид реально выбранного локального кандидата.
Конфиденциальность и отказ
Сетевые кандидаты могут содержать сведения о маршрутах клиента. Не публикуйте полный signaling log как безобидный пример без анализа. В учебной панели мы не выводим адреса; для диагностики достаточно состояния и типа маршрута. Доступ к комнате и endpoint конфигурации проверяется сервером, но политика журналов и хранения требует отдельного решения перед выпуском.
Если ICE завершается неудачно, бесконечная генерация новых offer без изменения условий обычно не помогает. Нужно различить отсутствие кандидатов, отказ TURN-credentials, недоступный сервер и потерю signaling. Позднее restartIce станет контролируемым действием восстановления. Сейчас результат урока состоит в правильной доставке кандидатов, явной настройке серверов и отказе от обещания, будто STUN решает все сетевые ограничения.
Конфигурация endpoint не должна превращаться в произвольный пользовательский список чужих серверов. В учебном проекте ею управляет владелец локального процесса. Для публичного продукта выбирается разрешённая инфраструктура, где известны стоимость, срок credentials и обработка отказов. Такое владение помогает объяснить человеку условия связи и не передавать его медиатрафик случайному ретранслятору, выбранному по неподтверждённой строке из клиентского запроса.
Оглавление серии · Исходные состояния проекта.
API сверены с первичной документацией 11 октября 2026 года. Пример имеет статус not_run: фактический запуск и наблюдение результата читателю предстоит выполнить самостоятельно.