html текст
All interests
  • All interests
  • Design
  • Food
  • Gadgets
  • Humor
  • News
  • Photo
  • Travel
  • Video
Click to see the next recommended page
Like it
Don't like
Add to Favorites

WebRTC PeerConnection и DataChannel: обмен данными между браузерами tutorial

image

Многие слышали о проекте WebRTC, поэтому я не буду углубляться в описание. На днях мне захотелось попробовать отправлять сообщения между браузерами, и чтобы разобраться в этом, я решил написать примитивный P2P-чат. Эксперимент удалался, и по мотивам я решил написать этот пост. На Хабре уже были статьи, освещающие вопросы использования WebRTC для передачи видео, однако меня в первую (и последнюю) очередь интересовала возможность обмена текстовыми или бинарными данными.

Для общения между клиентами мы будем использовать RTCPeerConnection (для установления соединения) и RTCDataChannel (для передачи данных). В процессе нам также понадобятся RTCIceCandidate и RTCSessionDescription, но об этом позже.

Поддержка протокола DataChannel появилась в браузерах совсем недавно, поэтому для того, чтобы все это работало, нужен Firefox 19+ или Chrome 25+. Однако в Firefox < 22 WebRTC по умолчанию отключен (нужно установить параметр media.peerconnection.enabled в true), а Chrome 25 нужно запускать с флагом --enable-data-channels. Я не стал оглядываться на них, и этот пост ориентирован на Firefox 22+ и Chrome 26+. Opera 15 поддержки WebRTC не имеет.

Поехали


Так как все это находится в разработке и конструкторы в Firefox и Chrome имеют префиксы moz и webkit соотвественно, давайте наведем небольшой порядок:

window._RTCPeerConnection = window.webkitPeerConnection00 || window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.RTCPeerConnection || window.PeerConnection;
window._RTCIceCandidate = window.webkitRTCIceCandidate || window.mozRTCIceCandidate || window.RTCIceCandidate;
window._RTCSessionDescription = window.webkitRTCSessionDescription || window.mozRTCSessionDescription || window.RTCSessionDescription;

Реализации в Firefox и Chrome на сегодняшний день отличаются, поэтому нам потребуется определять браузер.

var browser = {
  mozilla: /firefox/i.test(navigator.userAgent),
  chrome: /chrom(e|ium)/i.test(navigator.userAgent)
};

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


image


  • первый клиент через сервер отправляет Offer второму клиенту;
  • второй клиент через сервер отправляет Answer первому;
  • теперь клиенты знают друг о друге и могут установить соединение.

В моем случае я использовал WebSocket для общения с сервером на node.js. Если это чат, то наш сервер помнит о каждом подключенном клиенте, умеет передавать данные от клиента клиенту и возвращать список подключенных клиентов (для новоприбывших пользователей).

Здесь я не буду приводить код сервера, т.к. это выходит за рамки статьи. Пусть интерфейс общения с нашим сервером на стороне клиента будет таким:

var observer = {
  // ...
  send: function(evt, data) {
    // используется для отправки данных другому участнику
    this._send(evt, data);
  },
  on: function(evt, callback) {
    // устанавливает обработчик события ("ICE" или "SDP", речь о них пойдет ниже)
    // ...
  }
  // ...
}

Создание подключения


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

Для начала соединения Алиса создает объект RTCPeerConnection (как вы помните, чуть выше мы сделали кроссбраузерный _RTCPeerConnection). Конструктору нужно передать два аргумента с параметрами, о которых я расскажу ниже.

var pc, channel;
var config = {
  iceServers: [{ url: !browser.mozilla ?  "stun:stun.l.google.com:19302" : "stun:23.21.150.121" }]
};
var constrains = {
  options: [{ DtlsSrtpKeyAgreement: true }, { RtpDataChannels: true }]
};

function createPC(isOffer) {
  pc = new _RTCPeerConnection(config, constrains);

  // Сразу установим обработчики событий
  pc.onicecandidate = function(evt) { 
    if(evt.candidate) {
      // Каждый ICE-кандидат мы будем отправлять другому участнику через сигнальный сервер
      observer.send('ICE', evt.candidate);
    }
  };
  pc.onconnection = function() {
    // Пока это срабатывает только в Firefox
    console.log('Connection established');
  };
  pc.onclosedconnection = function() {
    // И это тоже. В Chrome о разрыве соединения придется узнавать другим способом
    console.log('Disconnected');
  };
  
  if(isOffer) {
    openOfferChannel();
    createOffer();
  } else {
    openAnswerChannel();
  }
}

// Алиса создает соединение
createPC(true);

Так как в реальном мире многие пользователи находятся за провайдерским NAT, в WebRTC предусмотрены способы его обхода (ICE, STUN, TURN). Первым параметром config передается объект с массивом STUN и/или TURN серверов. Можно использовать публичные, можно поднять свои. Я использовал STUN-сервер от Google. Кстати, если я правильно понял, сегодня в Firefox имеются проблемы с использованием доменных STUN-серверов, поэтому в нем рекомендуют использовать другие.
Параметр constrains необязательный, в нем передаются настройки соединения. Про опцию DtlsSrtpKeyAgreement можно почитать здесь, а опция RtpDataChannels, судя по всему, нужна для Chrome 25 (и, быть может, еще каких-то версий). В 28 у меня работало и без нее.

Для установки соединения участникам необходимо обменяться ICE-кандидатами через сигнальный сервер (в них содержатся данные о сетевом интерфейсе, адрес и др.). При появлении каждого кандидата будет срабатывать событие pc.onicecandidate (оно начнет срабатывать после установки локальной сессии методом setLocalDescription, о чем речь пойдет ниже).

Готовимся принимать кандидаты другого участника:

observer.on('ICE', function(ice) {
  // добавляем пришедший ICE-кандидат
  pc.addIceCandidate(new _RTCIceCandidate(ice));
});

Дальше Алиса создает канал. Именно этот канал и будет использоваться для передачи данных:

function openOfferChannel() {
  // Первый параметр – имя канала, второй - настройки. В настоящий момент Chrome поддерживает только UDP-соединения (non-reliable), а Firefox – и UDP, и TCP (reliable)
  channel = pc.createDataChannel('RTCDataChannel', browser.chrome ? {reliable: false} : {});
  // Согласно спецификации, после создания канала клиент должен установить binaryType в "blob", но пока это поддерживает только Firefox (Chrome выбрасывает ошибку)
  if(browser.mozilla) channel.binaryType = 'blob'; 
  setChannelEvents();
}
function setChannelEvents() {
  channel.onopen = function() {
    console.log('Channel opened');
  };
  channel.onclose = function() {
    console.log('Channel closed');
  };
  channel.onerror = function(err) {
    console.log('Channel error:', err);
  };
  channel.onmessage = function(e) {
    console.log('Incoming message:', e.data);
  };
}

Следующим шагом Алиса создает и отправляет Бобу «Offer» (описание сессии с различной служебной информацией, SDP).

function createOffer() {
  pc.createOffer(function(offer) {
    pc.setLocalDescription(offer, function() {
      // Отправляем другому участнику через сигнальный сервер
      observer.send('SDP', offer);
      // После завершения этой функции начнет срабатывать событие pc.onicecandidate
    }, function(err) {
      console.log('Failed to setLocalDescription():', err);
    });
  }, function(err) {
    console.log('Failed to createOffer():', err);
  });
}

Теперь Алиса ждет сессию Боба от сигнального сервера. Когда это случится, вызовется функция setRemoteSDP.

function setRemoteSDP(sdp) {
  pc.setRemoteDescription(new _RTCSessionDescription(sdp), function() {
    if(pc.remoteDescription.type == 'offer') {
      // Это выполнится у Боба
      createAnswer();
    }
  }, function(err) {
    console.log('Failed to setRemoteDescription():', err);
  });
}

observer.on('SDP', function(sdp) {
  if(!pc) {
    // Пришел Offer от другого участника
    // Боб создает соединение
    createPC(false);
  }
  setRemoteSDP(sdp);
});

Тем временем Боб получает от сигнального сервера сессию Алисы, со своей стороны создает объект RTCPeerConnection и готовится принять канал (это вызывается из функции createPC).

function openAnswerChannel() {
  pc.ondatachannel = function(e) {
    channel = e.channel;
    if(browser.mozilla) channel.binaryType = 'blob';

    setChannelEvents();
  };
}

Наконец, Боб сохраняет сессию Алисы, создает свою и отправляет ее Алисе.

function createAnswer() {
  pc.createAnswer(function(offer) {
    pc.setLocalDescription(offer, function() {
      // Отправляем другому участнику через сигнальный сервер
      observer.send('SDP', offer);
    }, function(err) {
      console.log('Failed to setLocalDescription():', err);
    });
  }, function(err) {
    console.log('Failed to createAnswer():', err);
  });
}

Обмен сообщениями


После успешного выполнения setRemoteDescription() у обоих участников и обмена ICE-кандидатами соединение между Алисой и Бобом должно установиться. В этом случае в Chrome и Firefox сработает событие channel.onopen, а в Firefox – еще и pc.onconnection.

Теперь Алиса и Боб могут обмениваться сообщениями с помощью метода channel.send():

channel.send("Hi there!");

При получении сообщения сработает событие channel.onmessage.

Определение дисконнекта


Когда другой участник завершает соедниение, в Firefox срабатывает сразу два события: pc.onclosedconnection и channel.onclose.
А вот в Chrome не срабатывает ничего, однако у объекта pc значение свойства iceConnectionState меняется на «disconnected» (по моим наблюдениям, меняется не сразу, а через несколько секунд). Поэтому придется сделать небольшой костыль, пока разработчики не исправили вызов события.

if(browser.chrome) {
  setInterval(function() {
    if(pc.iceConnectionState == "disconnected") {
      console.log("Disconnected");
    }
  }, 1000);
}

Текущие проблемы


  • Хочу обратить внимание, что на сегодняшний день Chrome может отправлять данные длиной не более ~1100 байт. Поэтому, чтобы отправить что-то большее, придется делить сообщение и отправлять частями. Firefox уже умеет отправлять большие сообщения, у него таких проблем нет.
  • Еще одним серьезным недостатком является то, что пока Chrome и Firefox несовместимы между собой (setRemoteDescription() с сессией другого браузера выбросит ошибку, соединение не установится).
  • Теоретически, таким способом можно отправлять как текстовые, так и бинарные данные. В Firefox с этим проблем нет, а ситуация с Chrome непонятная: в интернете пишут, что бинарные данные не отправляются, и ждут, когда разработчики это исправят, однако мне в Chrome 28 удалось как успешно отправить, так и принять их. Может быть, я чего-то не понимаю.

Заключение


Технология кажется мне очень перспективной, и уже сейчас можно начинать экспериментировать с ее внедрением, хоть и с существенными ограничениями.
А вот и ссылка на простенький чат, процесс создания которого и вдохновил меня на эту статью. Я писал его исключительно для тренировки и изучения WebRTC, и в Chrome он не сможет отправлять более ~1100 байт (разбивку я не делал).

Источники информации:
Читать дальше
Twitter
Одноклассники
Мой Мир

материал с habrahabr.ru

3

      Add

      You can create thematic collections and keep, for instance, all recipes in one place so you will never lose them.

      No images found
      Previous Next 0 / 0
      500
      • Advertisement
      • Animals
      • Architecture
      • Art
      • Auto
      • Aviation
      • Books
      • Cartoons
      • Celebrities
      • Children
      • Culture
      • Design
      • Economics
      • Education
      • Entertainment
      • Fashion
      • Fitness
      • Food
      • Gadgets
      • Games
      • Health
      • History
      • Hobby
      • Humor
      • Interior
      • Moto
      • Movies
      • Music
      • Nature
      • News
      • Photo
      • Pictures
      • Politics
      • Psychology
      • Science
      • Society
      • Sport
      • Technology
      • Travel
      • Video
      • Weapons
      • Web
      • Work
        Submit
        Valid formats are JPG, PNG, GIF.
        Not more than 5 Мb, please.
        30
        surfingbird.ru/site/
        RSS format guidelines
        500
        • Advertisement
        • Animals
        • Architecture
        • Art
        • Auto
        • Aviation
        • Books
        • Cartoons
        • Celebrities
        • Children
        • Culture
        • Design
        • Economics
        • Education
        • Entertainment
        • Fashion
        • Fitness
        • Food
        • Gadgets
        • Games
        • Health
        • History
        • Hobby
        • Humor
        • Interior
        • Moto
        • Movies
        • Music
        • Nature
        • News
        • Photo
        • Pictures
        • Politics
        • Psychology
        • Science
        • Society
        • Sport
        • Technology
        • Travel
        • Video
        • Weapons
        • Web
        • Work

          Submit

          Thank you! Wait for moderation.

          Тебе это не нравится?

          You can block the domain, tag, user or channel, and we'll stop recommend it to you. You can always unblock them in your settings.

          • habrahabr.ru
          • домен habrahabr.ru

          Get a link

          Спасибо, твоя жалоба принята.

          Log on to Surfingbird

          Recover
          Sign up

          or

          Welcome to Surfingbird.com!

          You'll find thousands of interesting pages, photos, and videos inside.
          Join!

          • Personal
            recommendations

          • Stash
            interesting and useful stuff

          • Anywhere,
            anytime

          Do we already know you? Login or restore the password.

          Close

          Add to collection

             

            Facebook

            Ваш профиль на рассмотрении, обновите страницу через несколько секунд

            Facebook

            К сожалению, вы не попадаете под условия акции