7 дек. 2012 г.

p2p во Flash. протокол RTMFP



–Ты знаешь, – начала Алина, – сколько помню себя как разработчика...
– Как разработчика? – переспросил Антон, делая большой глоток пива из бокала, – и давно ты в разработчиках?
– Достаточно, но к делу это отношения не имеет, по сути. Так вот. Все это время, каждый год Flash кто-нибудь хоронит. Все время идут разговоры о том, что он безбожно устарел, и вот появляется что-то – то SilverLight, то HTML5, – что должно стремительно его заменить. Но флеш почему-то совершенно не собирается умирать. Наоборот, он постоянно выпускает что-то невероятное, что его воскрешает в новом качестве. То ActionScript3, то поддержку 3D видео, или вот, например, p2p протокол RTMFP.



Ироничная улыбочка с лица Антона куда-то незаметно сползла, уступив место отвисшей челюсти. Образ молодой, привлекательной девушки в короткой юбке, сидящей на его рабочем столе, как-то совершенно не вязался с такими словами как P2P и ActionScript. Антон понял, что впадает в ступор, и, чтобы как-то заполнить неловкую паузу, спросил. RTMFP? – а можешь рассказать о нем поподробнее? Конечно, весело подхватила девушка, я на нем не одну собаку уже съела.

Это, как я уже говорила, P2P протокол. Главная его особенность в том, что все данные передаются непосредственно от пользователя к пользователю, минуя сервер. Он работает на UDP и за счет его фишек с широковещательной адресацией позволяет коннектиться даже юзерам, сидящим за NAT-ами. Но если в сетке зарублен UDP трафик, тут уж извиняй, работать не будет вообще. Для работы нужен флеш версии не ниже 10.1 и специальный rendezvous-сервер, который позволяет нодам найти друг друга. Подсоединившись к серверу, пользователь получает специальный peerID, который его собственно и идентифицирует в образовавшейся сети.

Значит, по сервакам. Можно заюзать Adobe Flash Media Saerver, в простонародье FMS. Кажется, с версии 4.5 там появилась поддержка RTMFP. Милая вещица, хорошо работает, возможен серверный скриптинг на том же ActionScript, но в лицензии стоит каких-то астрономических денег. Есть фришный аналог: Cumulus. Достаточно серьезный проект, с хорошей поддержкой. Есть что-то еще, но на тот момент, когда я искала, там все было совсем плохо. В Cumulus есть только одна проблема, которая может слегка напрягать по первости: серверные скрипты пишутся на языке LUA. Впрочем, язык очень даже годный и в освоении не особо сложен. Вот, ну а на периоде разработки и отладки можно с чистой совестью и широкой улыбкой юзать совершенно бесплатную штуку от того же Adobe – Cirrus. http://labs.adobe.com/technologies/cirrus/. Там регишься, получаешь developer key и все. Если надумаешь работать с FMS, то его можно брать в аренду на Amazon EC2. Но я в свое время остановилась на Cumulus и, как говорится, осталась с почтением.

С клиентской стороны все работает достаточно просто.

Нужно создать объект NetConnection и подключиться к серверу:



var connectUrl:String = "rtmfp://p2p.rtmfp.net";
var DeveloperKey:String = "твой ключ";

netConnection = new NetConnection();
netConnection.connect(connectUrl, DeveloperKey);


примерно так. Конечно, надо отлавливать события, он ведь может и не соединиться:

тогда добавляется строчка:



netConnection = new NetConnection();
netConnection.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
netConnection.connect(connectUrl, DeveloperKey);


и собственно метод обработчик.

public function netStatusHandler(event:NetStatusEvent):void



в event.info.code будет код события. т.е. рабочий вариант функции будет выглядеть примерно так:
public function netStatusHandler(event:NetStatusEvent):void{

    
 switch (event.info.code)
             {
  case "NetConnection.Connect.Success":
   peerID = nc.nearID;
   //Что то делаем дальше   
              break;

  case "NetConnection.Connect.Failed":
   //наступает, если не удалось подсоедениться к серверу. Например, если закрыт
   //UDP. Таймаут около минуты
  break;
 }
}


что можно делать дальше? Тут открывается несколько вариантов. Предположим, мы хотим сделать что-то вроде видеочата. т.е. транслировать видео и звук с камеры и микрофона другим пользователям и получать такие же потоки от них. Тогда нужно определиться вот с чем, RTMFP предлагает на выбор два варианта взаимодействия: прямые соединения и группы. Во время прямого соединения между двумя пирами устанавливается некое виртуальное соединение и они могут обмениваться данными между собой. Все данные которые ты передаешь попадают одному и только одному клиенту. Второй вариант это группы. Тут все немного сложнее, пользователи не устанавливают прямых соединений а объединяются в группы в которых обмен данными идет по принципу торрента, т.е. каждый участник в зависимости от ситуации может участвовать в передаче данных. т.е. группа получает трафик и дальше дербанит его межу собой в зависимости от взаимного расположения и толщины каналов участников этой группы. Схематически это выглядит вот так:


Плюсы и минусы: Передача данных в группах работает медленно. Прямые соединения гораздо быстрее, но, в обмен группы представляют возможность вещать сразу на огромную аудиторию. Например, один профессор вещает свою видео лекцию в сети а его смотрят сразу же десять тысяч студентов. Реально он отдает трафик одному двум пирам, а они уже передают дальше еще какому-то количеству участников и те в свою очередь тоже и т.д. Прямые соединения значительно быстрее но соединиться можно лишь с таким количеством пиров, которое вытянет твой канал. Выбирать конечно нужно под конкретную ситуацию. Давай разберем оба варианта и начнем с прямых соединений, так как они проще в реализации и понимании.

После успешного соединения с сервером создадим объект NetStream для передачи своего потока. Для этого добавим в netStatusHandler в секции "NetConnection.Connect.Success" следующий код:


case "NetConnection.Connect.Success":
 outStream = new NetStream(netConnection, NetStream.DIRECT_CONNECTIONS);
 outStream.addEventListener(NetStatusEvent.NET_STATUS,  netStatusHandler);
 outStream.attachAudio(Microphone.getMicrophone()); 
 outStream.attachCamera(Camera.getCamera());
 outStream.publish("MediaChannel");  
break;


еще добавим в наш класс пару переменных что бы это все заработало


public var netConnection:NetConnection;
private var outStream:NetStream;


теперь в netStatusHandler будут обрабатываться еще и события исходящего потока. А Любой пир подключенный к этому серверу сможет начать просмотр, конечно если знает peerID вещателя и имя канала, в нашем случае "MediaChannel"

делается это так:

private var inStream:NetStream;
inStream = new NetStream(netConnection, id_of_publishing_client);
inStream.addEventListener(NetStatusEvent.NET_STATUS, netStreamHandler);
recvStream.play("MediaChannel"); 



звук заиграет сам по себе а вот видео можно будет снять при помощи объекта Video, например так:

public var screen:Video;

screen = new Video();
screen.width = stage.stageWidth;
screen.height = stage.stageHeight;
screen.scaleX < screen.scaleY ? screen.scaleY = screen.scaleX : screen.scaleX = screen.scaleY;
addChild(screen);
screen.attachNetStream(inStream);


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

Раздающий, может решать кому показывать стрим а кому нет. При создании outStream можно добавить вот такой объект:

var o:Object = new Object();

o.onPeerConnect = function(subscriberStream:NetStream):Boolean {
 if(accept) {
  return true;
 } else {
  return false;
 }
}
outStream.client = o;


здесь subscriberStream – это копия объекта NetStream запрашивающего соединение. Можно выудить из него peerID обращающегося subscriberStream.farID и на основе этого каким-то образом принять решение об отказе или принятии соединения. Для отказа достаточно вернуть false;

с помощью свойства netConnection.maxPeerConnections можно устанавливать число подписчиков способных подключиться одновременно. По умолчанию там стоит 8.

Ну вот, теперь о группах.

В начале нам нужно создать группу, делается это так:

private var groupSpecifier:GroupSpecifier;
private var netGroup:NetGroup;

groupSpecifier = new GroupSpecifier("com.example.p2p");
groupSpecifier.multicastEnabled = true;
groupSpecifier.objectReplicationEnabled = true;
groupSpecifier.postingEnabled = true; 
groupSpecifier.routingEnabled = true; 
groupSpecifier.serverChannelEnabled = true; 
groupSpecifier.ipMulticastMemberUpdatesEnabled = true;
   
netGroup = new NetGroup(netConnection,groupSpecifier.groupspecWithAuthorizations());
netGroup.addEventListener(NetStatusEvent.NET_STATUS,netStatusHandler);


тут в начале мы создаем объект groupSpecifier, выставляем в нем всевозможные параметры и потом собственно подключаемся. Следует помнить что группа описывается ее именем, в нашем случае это "com.example.p2p" и набором параметров, т. е. если кто-то подключится к группе у которой имя будет тем-же а вот скажем postingEnabled будет выставлен в false то «по факту» он подключится уже к другой группе. Соответственно если группы ранее не существовало, то она создается, если уже кем-то была создана, то происходит просто подключение.


О том что подключение к группе произошло успешно нам поведает событие "NetGroup.Connect.Success" в netStatusHandler(). Псоле успешного соединения можно начинать что ни будь транслировать и/или смотреть. Точно так же как и в предыдущем случае создаем NetStream только коннектимся не к пиру а к ргуппе.

netStream = new NetStream(netConnection, groupSpecifier.groupspecWithAuthorizations());

далее все как и раньше.

netStream.publish() или netStream.play()

Что еще важно знать о группах. В группы можно постить сообщения. Причем, передавать можно достаточно приличные объемы данных. К примеру, если ты хочешь передавать видео не с камеры а из файла, то тут иначе просто не обойтись. Нельзя закачать видео даже в flv формате в NetStream так, что бы он начал его транслировать, удивительно но факт: Его сперва нужно передать в виде сообщения а потом на принимающей стороне загнать в NetStream. Делается все это примерно так:

var message:Object = new Object();
message.seq = ++sec;
message.peer = netConnection.nearID;
message.text = "Some text message";
netGroup.post(message);


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

public function netStatusHandler(event:NetStatusEvent):void{

...

case "NetGroup.Posting.Notify":

 if(event.info.message.hasOwnProperty('peer'))
 {
  trace(event.info.message.text);
 }
break;

... 
}

вот, в общих чертах все. Если будет интересно, полазай по вот этим полезным ссылочкам, там раскрыта куча полезных фишек и подробностей...


Антон открыл глаза и подскочил с постели. Эх, ну и приснится же, девушка-программист, рассказывающая про какую-то хрень. Как там его, RTMFP, хах, реалистичное название. Уф. Он вытер со лба холодный пот и подошел к компьютеру. Так, гугл, интересно, есть на самом деле такое сокращение? RTMFP... О, блин, и правда что-то есть! Real Time Media Flow Protocol — и правда p2p для флеша... Но... но этого же просто не может быть! Антон почувствовал, как пол уходит куда-то из-под ног. Так, какие она там кидала ссылки?

2 комментария:

Unknown комментирует...

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

Unknown комментирует...

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