Рубрики
Без рубрики

Руководство по WebRTC

Узнайте, как создать прямой канал связи в реальном времени между двумя браузерами, мобильными приложениями или другими HTML-клиентами с помощью WebRTC.

Автор оригинала: baeldung.

1. Обзор

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

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

Мы будем использовать HTML, JavaScript и библиотеку WebSocket вместе со встроенной поддержкой WebRTC в веб-браузерах для создания клиента. И мы будем строить сигнальный сервер с Spring Boot, используя WebSocket в качестве протокола связи. Наконец, мы увидим, как добавить видео-и аудиопотоки в это соединение.

2. Основы и концепции WebRTC

Давайте посмотрим, как два браузера взаимодействуют в типичном сценарии без WebRTC.

Предположим, у нас есть два браузера, и Браузеру 1 необходимо отправить сообщение в Браузер 2 . Браузер 1 сначала отправляет его на Сервер :

После того, как Сервер получит сообщение, он обработает его , найдет Браузер 2 и отправит ему сообщение:

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

WebRTC решает эту проблему, создавая прямой канал между двумя браузерами, устраняя необходимость в сервере :

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

3. Поддержка WebRTC и встроенных функций

WebRTC поддерживается основными браузерами, такими как Chrome, Firefox, Opera и Microsoft Edge, а также платформами, такими как Android и iOS.

WebRTC не нуждается в установке каких-либо внешних плагинов в вашем браузере, так как решение поставляется в комплекте с браузером.

Кроме того, в типичном приложении реального времени, включающем передачу видео и аудио, мы должны сильно зависеть от библиотек C++, и нам приходится решать множество проблем, в том числе:

  • Сокрытие потери пакетов
  • Подавление эха
  • Адаптивность полосы пропускания
  • Динамическая буферизация дрожания
  • Автоматическая регулировка усиления
  • Снижение и подавление шума
  • Изображение “очистка”

Но WebRTC справляется со всеми этими проблемами под капотом , что упрощает связь между клиентами в режиме реального времени.

4. Одноранговое соединение

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

Чтобы установить одноранговое соединение, необходимо выполнить несколько шагов, позволяющих клиентам:

  • сделайте себя доступными для общения
  • идентифицируйте друг друга и делитесь информацией, связанной с сетью
  • поделитесь и согласуйте формат используемых данных, модель и протоколы
  • общий доступ к данным

WebRTC определяет набор API и методологий для выполнения этих шагов.

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

5. Сигнализация

Сигнализация относится к процессам, связанным с обнаружением сети, созданием сеанса, управлением сеансом и обменом метаданными о возможностях МУЛЬТИМЕДИА.

Это очень важно, так как клиенты должны знать друг друга заранее, чтобы начать общение.

Для достижения всех этих целей WebRTC не определяет стандарт сигнализации и оставляет его на реализацию разработчиком. Таким образом, это обеспечивает нам гибкость использования WebRTC на ряде устройств с любой технологией и поддерживающим протоколом.

5.1. Построение сервера сигнализации

Для сервера сигнализации мы построим сервер WebSocket с помощью Spring Boot . Мы можем начать с пустого загрузочного проекта Spring, созданного из Spring Initializr .

Чтобы использовать WebSocket для нашей реализации, давайте добавим зависимость в ваш pom.xml :


    org.springframework.boot
    spring-boot-starter-websocket
    2.4.0

Мы всегда можем найти последнюю версию для использования в Maven Central .

Реализация сервера сигнализации проста — мы создадим конечную точку, которую клиентское приложение может использовать для регистрации в качестве подключения WebSocket.

Чтобы сделать это в Spring Boot, давайте напишем класс @Configuration , который расширяет WebSocketConfigurer и переопределяет метод registerWebSocketHandlers :

@Configuration
@EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new SocketHandler(), "/socket")
          .setAllowedOrigins("*");
    }
}

Обратите внимание, что мы определили /сокет как URL-адрес, который мы зарегистрируем у клиента, который мы будем создавать на следующем шаге. Мы также передали SocketHandler в качестве аргумента методу AddHandler — на самом деле это обработчик сообщений, который мы создадим следующим.

5.2. Создание обработчика сообщений на Сервере сигнализации

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

Это необходимо для облегчения обмена метаданными между различными клиентами для установления прямого подключения WebRTC .

Здесь, чтобы все было просто, когда мы получим сообщение от клиента, мы отправим его всем другим клиентам, кроме самого себя.

Для этого мы можем расширить TextWebSocketHandler из библиотеки Spring WebSocket и переопределить оба метода handleTextMessage и afterConnectionEstablished :

@Component
public class SocketHandler extends TextWebSocketHandler {

    Listsessions = new CopyOnWriteArrayList<>();

    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message)
      throws InterruptedException, IOException {
        for (WebSocketSession webSocketSession : sessions) {
            if (webSocketSession.isOpen() && !session.getId().equals(webSocketSession.getId())) {
                webSocketSession.sendMessage(message);
            }
        }
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        sessions.add(session);
    }
}

Как мы можем видеть в методе afterConnectionEstablished , мы добавляем полученный сеанс в список сеансов, чтобы мы могли отслеживать всех клиентов.

И когда мы получаем сообщение от любого из клиентов, как видно из handleTextMessage, мы перебираем все клиентские сеансы в списке и отправляем сообщение всем другим клиентам, кроме отправителя, сравнивая идентификатор сеанса отправителя и сеансы в списке.

6. Обмен метаданными

В P2P-соединении клиенты могут сильно отличаться друг от друга. Например, Chrome на Android может подключаться к Mozilla на Mac.

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

На этом этапе WebRTC использует SDP (Протокол описания сеанса) для согласования метаданных между клиентами.

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

Соединение устанавливается по завершении этого процесса.

7. Настройка клиента

Давайте создадим наш клиент WebRTC таким образом, чтобы он мог действовать как в качестве инициирующего однорангового узла, так и в качестве удаленного однорангового узла.

Мы начнем с создания HTML-файла под названием index.html и файл JavaScript с именем client.js который index.html будет использовать.

Чтобы подключиться к нашему сигнальному серверу, мы создаем с ним соединение WebSocket. Предполагая, что сервер сигнализации весенней загрузки, который мы построили, работает на http://localhost:8080 , мы можем создать соединение:

var conn = new WebSocket('ws://localhost:8080/socket');

Чтобы отправить сообщение на сигнальный сервер, мы создадим метод send , который будет использоваться для передачи сообщения в следующих шагах:

function send(message) {
    conn.send(JSON.stringify(message));
}

8. Настройка простого RTCDataChannel

После настройки клиента в client.js , нам нужно создать объект для класса RTCPeerConnection :

configuration = null;
var peerConnection = new RTCPeerConnection(configuration);

В этом примере целью объекта конфигурации является передача серверов STUN (утилиты обхода сеанса для NAT) и TURN (обход с использованием ретрансляторов вокруг NAT) и других конфигураций, которые мы обсудим в последней части этого руководства. Для этого примера достаточно передать null .

Теперь мы можем создать канал данных для передачи сообщений:

var dataChannel = peerConnection.createDataChannel("dataChannel", { reliable: true });

Впоследствии мы можем создавать прослушиватели для различных событий в канале данных:

dataChannel.onerror = function(error) {
    console.log("Error:", error);
};
dataChannel.onclose = function() {
    console.log("Data channel is closed");
};

9. Установление Связи С ICE

Следующий шаг в установлении соединения WebRTC включает протоколы ICE (Interactive Connection Establishment) и SDP, в которых описания сеансов одноранговых узлов обмениваются и принимаются обоими одноранговыми узлами.

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

9.1. Создание предложения

Во-первых, мы создаем предложение и устанавливаем его в качестве локального описания однорангового соединения . Затем мы отправляем предложение другому одноранговому узлу:

peerConnection.createOffer(function(offer) {
    send({
        event : "offer",
        data : offer
    });
    peerConnection.setLocalDescription(offer);
}, function(error) {
    // Handle error here
});

Здесь метод send вызывает сервер сигнализации для передачи информации offer .

Обратите внимание, что мы можем свободно реализовать логику метода отправки с любой серверной технологией.

9.2. Обращение с кандидатами на ЛЕД

Во-вторых, нам нужно разобраться с кандидатами на ЛЕД. WebRTC использует протокол ICE (Interactive Connection Establishment) для обнаружения одноранговых узлов и установления соединения.

Когда мы устанавливаем локальное описание на одноранговое соединение , оно запускает событие icecandidate .

Это событие должно передать кандидата удаленному одноранговому узлу, чтобы удаленный одноранговый узел мог добавить его в свой набор удаленных кандидатов.

Для этого мы создаем прослушиватель для события onicecandidate :

peerConnection.onicecandidate = function(event) {
    if (event.candidate) {
        send({
            event : "candidate",
            data : event.candidate
        });
    }
};

Событие icecandidate снова запускается с пустой строкой кандидата, когда все кандидаты собраны.

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

Кроме того, то же самое событие запускается снова, чтобы указать, что сбор кандидатов ICE завершен со значением candidate object, установленным в null для события. Это не нужно передавать удаленному узлу.

9.3. Прием кандидата на ICE

В-третьих, нам нужно обработать кандидата ICE, отправленного другим сверстником.

Удаленный одноранговый узел, получив это кандидат , следует добавить его в свой пул кандидатов:

peerConnection.addIceCandidate(new RTCIceCandidate(candidate));

9.4. Получение Оферты

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

peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
peerConnection.createAnswer(function(answer) {
    peerConnection.setLocalDescription(answer);
        send({
            event : "answer",
            data : answer
        });
}, function(error) {
    // Handle error here
});

9.5. Получение ответа

Наконец, инициирующий узел получает ответ и устанавливает его в качестве удаленного описания :

handleAnswer(answer){
    peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
}

С помощью этого WebRTC устанавливает успешное соединение.

Теперь мы можем отправлять и получать данные между двумя одноранговыми узлами напрямую , без сервера сигнализации.

10. Отправка сообщения

Теперь, когда мы установили соединение, мы можем отправлять сообщения между одноранговыми узлами, используя метод send канала данных :

dataChannel.send("message");

Аналогично, чтобы получить сообщение от другого однорангового узла, давайте создадим прослушиватель для события onmessage :

dataChannel.onmessage = function(event) {
    console.log("Message:", event.data);
};

Чтобы получить сообщение по каналу данных, мы также должны добавить обратный вызов для объекта PeerConnection :

peerConnection.ondatachannel = function (event) {
    dataChannel = event.channel;
};

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

11. Добавление видео-и аудиоканалов

Когда WebRTC устанавливает P2P-соединение, мы можем легко передавать аудио-и видеопотоки напрямую.

11.1. Получение медиа-потока

Во-первых, нам нужно получить медиапоток из браузера. WebRTC предоставляет API для этого:

const constraints = {
    video: true,audio : true
};
navigator.mediaDevices.getUserMedia(constraints).
  then(function(stream) { /* use the stream */ })
    .catch(function(err) { /* handle the error */ });

Мы можем указать частоту кадров, ширину и высоту видео с помощью объекта ограничения.

Объект ограничения также позволяет указать камеру, используемую в случае мобильных устройств:

var constraints = {
    video : {
        frameRate : {
            ideal : 10,
            max : 15
        },
        width : 1280,
        height : 720,
        facingMode : "user"
    }
};

Кроме того, значение facing Mode можно установить в “environment” вместо “user” , если мы хотим включить заднюю камеру.

11.2. Отправка потока

Во-вторых, мы должны добавить поток к объекту однорангового соединения WebRTC:

peerConnection.addStream(stream);

Добавление потока в одноранговое соединение запускает addstream событие на подключенных одноранговых узлах.

11.3. Получение потока

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

Давайте установим этот поток в виде элемента HTML-видео:

peerConnection.onaddstream = function(event) {
    videoElement.srcObject = event.stream;
};

12. Проблемы с NAT

В реальном мире устройства брандмауэра и NAT (обход сетевых адресов) подключают наши устройства к общедоступному Интернету.

NAT предоставляет устройству IP-адрес для использования в локальной сети. Таким образом, этот адрес недоступен за пределами локальной сети. Без публичного адреса сверстники не могут общаться с нами.

Для решения этой проблемы WebRTC использует два механизма:

  1. ОГЛУШАТЬ
  2. ОЧЕРЕДЬ

13. Использование ОГЛУШЕНИЯ

ОГЛУШЕНИЕ-самый простой подход к этой проблеме. Прежде чем поделиться сетевой информацией с одноранговым узлом, клиент делает запрос на сервер ОГЛУШЕНИЯ. Ответственность сервера STUN заключается в том, чтобы вернуть IP-адрес, с которого он получает запрос.

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

Чтобы использовать сервер ОГЛУШЕНИЯ, мы можем просто передать URL-адрес в объекте конфигурации для создания объекта RTCPeerConnection :

var configuration = {
    "iceServers" : [ {
        "url" : "stun:stun2.1.google.com:19302"
    } ]
};

14. Использование ПОВОРОТА

В отличие от этого, TURN-это резервный механизм, используемый, когда WebRTC не может установить соединение P2P. Роль сервера TURN заключается в передаче данных непосредственно между одноранговыми узлами. В этом случае фактический поток данных проходит через серверы TURN. Используя реализации по умолчанию, серверы поворота также действуют как серверы ОГЛУШЕНИЯ.

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

Но использование TURN-сервера на самом деле не является P2P-соединением, так как присутствует промежуточный сервер.

Примечание: ПОВОРОТ-это последнее средство, когда мы не можем установить соединение P2P. Поскольку данные проходят через сервер TURN, для этого требуется большая пропускная способность, и в данном случае мы не используем P2P.

Аналогично STUN, мы можем предоставить URL-адрес сервера ПОВОРОТА в том же объекте конфигурации:

{
  'iceServers': [
    {
      'urls': 'stun:stun.l.google.com:19302'
    },
    {
      'urls': 'turn:10.158.29.39:3478?transport=udp',
      'credential': 'XXXXXXXXXXXXX',
      'username': 'XXXXXXXXXXXXXXX'
    },
    {
      'urls': 'turn:10.158.29.39:3478?transport=tcp',
      'credential': 'XXXXXXXXXXXXX',
      'username': 'XXXXXXXXXXXXXXX'
    }
  ]
}

15. Заключение

В этом уроке мы обсудили, что такое проект WebRTC, и представили его основные концепции. Затем мы создали простое приложение для обмена данными между двумя HTML-клиентами.

Мы также обсудили шаги, связанные с созданием и установлением подключения WebRTC.

Кроме того, мы рассмотрели использование серверов STUN и TURN в качестве резервного механизма при сбое WebRTC.

Вы можете ознакомиться с примерами, приведенными в этой статье на GitHub .