Перейти к основному содержимому

4-02 Установка WebRTC-соединения: offer, answer и ICE

04-04-02

В этом разделе подробно рассматривается процесс установления однорангового (peer-to-peer, P2P) соединения между двумя участниками с использованием технологии WebRTC. Хотя на первый взгляд WebRTC может казаться простой «видеосвязью в браузере», на самом деле за кулисами происходит сложная координация множества протоколов. В данном тексте мы пошагово разберём, как браузеры договариваются о параметрах соединения, преодолевают сетевые ограничения и обеспечивают защищённую передачу медиаданных.


1. Общая архитектура установления WebRTC-соединения

WebRTC — это технология, позволяющая организовать прямое (P2P) соединение между двумя устройствами (например, браузерами) для передачи аудио, видео и произвольных данных. Однако сам WebRTC не включает в себя протокол сигнализации — механизм для обмена служебной информацией, такой как параметры соединения и сетевые адреса. Вместо этого разработчик должен реализовать или выбрать собственный способ обмена этой информацией, например, через WebSocket, HTTP или другой канал.

Соединение устанавливается между двумя участниками, условно называемыми Peer A и Peer B. Основные этапы включают:

  1. Инициализация соединения.
  2. Обмен описаниями сессии (SDP).
  3. Сбор и обмен сетевых кандидатов (ICE).
  4. Установление защищённого транспорта.
  5. Передача медиапотоков.

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


2. Инициализация и создание предложения (offer)

Процесс начинается с одной из сторон — например, Peer A. Она инициирует установление соединения, создавая объект RTCPeerConnection. Этот объект — центральный элемент WebRTC, отвечающий за всё: от согласования параметров до передачи данных и обработки безопасности.

const peerA = new RTCPeerConnection();

После инициализации объекта Peer A вызывает метод createOffer(). Этот метод генерирует специальное текстовое описание сессии, называемое SDP (Session Description Protocol). Такое описание содержит:

  • Поддерживаемые аудио- и видео-кодеки (например, Opus, VP8, H.264).
  • Направления передачи медиа (отправлять, принимать или оба).
  • Транспортные параметры (порты, протоколы).
  • Информацию о потоке данных (data channels, если используются).

💡 Что такое SDP?
SDP — это формат описания мультимедийных сессий. Он не передаёт медиаданные и не управляет соединением напрямую, а служит «языком переговоров» между участниками. Представьте, что два человека хотят поговорить: один говорит: «Я говорю по-английски и могу слышать и говорить», — это и есть SDP-предложение (offer).

После генерации SDP-описания оно называется offer (предложение). Но прежде чем отправить его, Peer A должен сохранить его как локальное описание — это нужно для внутренней синхронизации состояния соединения.

const offer = await peerA.createOffer();
await peerA.setLocalDescription(offer);

Метод setLocalDescription() сообщает WebRTC-движку, что данное описание теперь является текущим локальным состоянием соединения.


3. Передача предложения через сигналинговый сервер

Теперь Peer A должен передать offer стороне Peer B. Поскольку WebRTC не определяет, как именно происходит этот обмен, используется внешний сигналинговый канал — например, WebSocket-соединение с сервером.

// Пример: отправка offer через WebSocket
signalingSocket.send(JSON.stringify({ type: 'offer', sdp: offer }));

Сигналинговый сервер — это просто посредник. Он не участвует в медиатранспорте, а лишь пересылает служебные сообщения между участниками. Это важно: WebRTC остаётся P2P, даже если для настройки используется централизованный сервер.


4. Получение предложения и формирование ответа (answer)

Когда Peer B получает offer, он должен его обработать. Сначала он инициализирует свой собственный RTCPeerConnection:

const peerB = new RTCPeerConnection();

Затем он устанавливает полученное SDP как удалённое описание:

await peerB.setRemoteDescription(offer);

Это означает, что Peer B теперь знает, какие параметры предлагает Peer A, и может подготовить ответ, совместимый с этими параметрами.

Далее Peer B вызывает createAnswer() — метод, аналогичный createOffer(), но формирующий ответ на уже полученное предложение. Ответ также представляет собой SDP, но уже с учётом собственных возможностей Peer B.

const answer = await peerB.createAnswer();

После этого Peer B устанавливает ответ как своё локальное описание:

await peerB.setLocalDescription(answer);

И, наконец, отправляет answer обратно к Peer A через тот же сигналинговый канал:

signalingSocket.send(JSON.stringify({ type: 'answer', sdp: answer }));

Peer A, получив answer, устанавливает его как удалённое описание:

await peerA.setRemoteDescription(answer);

На этом этапе обе стороны знают, какие кодеки, направления и транспортные параметры будут использоваться. Однако соединение ещё не установлено — нужно решить, по какому сетевому пути передавать данные.


5. Сбор и обмен ICE-кандидатов

Даже если стороны договорились о параметрах, они могут находиться за NAT (Network Address Translation) — устройствами, которые скрывают внутренние IP-адреса. Это мешает прямому P2P-соединению, потому что внешние участники не могут напрямую обратиться к приватным адресам вроде 192.168.1.10.

Для решения этой проблемы используется ICE (Interactive Connectivity Establishment) — протокол, который находит все возможные пути (кандидаты) для соединения и проверяет, какой из них работает.

Как работает сбор кандидатов?

Каждая сторона (и Peer A, и Peer B) параллельно собирает ICE-кандидаты — возможные сетевые адреса, по которым её можно достичь. Эти кандидаты бывают трёх типов:

Тип кандидатаОписание
Host candidateЛокальный IP-адрес и порт (например, 192.168.1.10:50000). Работает только в пределах локальной сети.
Server reflexive candidateВнешний IP и порт, определённый через STUN-сервер (например, 203.0.113.5:60000). Позволяет узнать, как устройство видно извне.
Relayed candidateАдрес на TURN-сервере, через который идёт ретрансляция трафика. Используется, когда прямое соединение невозможно. Более затратно по ресурсам, но надёжно.

💡 STUN и TURN — вспомогательные протоколы

  • STUN (Session Traversal Utilities for NAT) помогает устройству узнать свой публичный IP и порт.
  • TURN (Traversal Using Relays around NAT) — это «запасной путь»: если прямое соединение не удаётся, трафик идёт через сервер-ретранслятор.
    ICE использует оба, чтобы максимизировать шансы на успешное соединение.

Каждый найденный кандидат передаётся другой стороне через сигналинговый канал. Например:

peerA.onicecandidate = (event) => {
if (event.candidate) {
signalingSocket.send(JSON.stringify({
type: 'ice-candidate',
candidate: event.candidate
}));
}
};

Peer B, получив кандидата, добавляет его в свой RTCPeerConnection:

await peerB.addIceCandidate(candidate);

То же самое происходит и в обратную сторону — обе стороны обмениваются кандидатами.


6. ICE-переговоры и выбор рабочего пути

После добавления кандидатов запускается ICE-процедура — система начинает проверять все возможные пары кандидатов (один от Peer A, один от Peer B), чтобы найти хотя бы один рабочий путь.

Например, проверяется:

  • Может ли Peer A отправить пакет на 203.0.113.5:60000 (STUN-адрес Peer B)?
  • Работает ли соединение через TURN-ретранслятор?

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

💡 Визуализация процесса
Представьте, что два человека хотят встретиться. Один предлагает: «Я могу прийти к метро А, Б или через такси». Второй говорит: «Я могу приехать к метро Б, В или вызвать такси». Они проверяют все комбинации и находят, что «метро Б» подходит обоим — это и есть выбранный путь.


7. Установление защищённого соединения: DTLS и SRTP

Когда транспортный путь найден, WebRTC автоматически инициирует DTLS-рукопожатие (Datagram Transport Layer Security) — аналог TLS, но для UDP. Этот процесс нужен для:

  • Аутентификации сторон.
  • Обмена криптографическими ключами.
  • Установления защищённого канала.

После успешного DTLS-рукопожатия генерируются ключи для SRTP (Secure Real-time Transport Protocol) — защищённой версии RTP, которая шифрует аудио- и видеопотоки.

💡 Почему SRTP?
Обычный RTP не шифрует данные. SRTP добавляет шифрование полезной нагрузки (например, видеофреймов), что критично для конфиденциальности. В WebRTC всегда используется SRTP, и ключи обмениваются через DTLS, а не в открытом виде.


8. Начало передачи медиапотоков

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

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

// Пример: добавление медиапотока в соединение
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
stream.getTracks().forEach(track => peerA.addTrack(track, stream));

Каждый медиапакет проходит по следующему стеку:

[Видео/Аудио] → Кодирование (VP8, Opus) → SRTP (шифрование) → UDP → Сеть → ICE (выбранный путь)

9. Итог: как все протоколы работают вместе

WebRTC — это не отдельный протокол, а интегрированная система, объединяющая несколько технологий:

ПротоколРоль в WebRTC
SDPОписание параметров сессии: кодеки, направления, порты.
ICEПоиск и проверка рабочих сетевых путей.
STUNОпределение публичного IP-адреса.
TURNРетрансляция трафика при невозможности прямого соединения.
DTLSШифрование и обмен ключами.
SRTPЗащищённая передача аудио и видео.
СигналингВнешний механизм обмена SDP и ICE-кандидатами (не стандартизирован).

💡 Важно понимать: WebRTC «прячет» сложность от пользователя, но для инженера важно видеть, что под капотом работают те же протоколы, что и в классических системах видеосвязи — просто в более автоматизированной форме.


Заключение

Установление WebRTC-соединения — это многоэтапный процесс, требующий слаженной работы нескольких протоколов. От createOffer до передачи первого видеокадра проходит несколько секунд, в течение которых:

  1. Стороны договариваются о параметрах через SDP.
  2. Находят рабочий сетевой путь с помощью ICE, STUN и TURN.
  3. Устанавливают защищённый канал с DTLS и SRTP.

Понимание этого процесса критически важно для диагностики проблем: например, если видеосвязь не устанавливается, нужно проверить, дошёл ли offer, прошёл ли ICE, успешно ли прошло DTLS. Каждый этап — это потенциальное «узкое место», которое можно проверить с помощью инструментов вроде Wireshark, логов браузера или встроенных WebRTC-статистик (getStats()).