Quantcast
Channel: Jozef Chúťka's blog » multiple
Viewing all articles
Browse latest Browse all 2

P2P Multicast in Flash Player 10!

$
0
0
P2PMulticast communication flow

P2PMulticast communication flow

P2P multicast in Flash Player 10, yeap… ready? This method creates n:n model of p2p communicators. So broadcasting data goes from broadcaster directly to all receivers. All users are broadcasters and receivers. There is no multilevel logic in it, so keep in mind the upload bandwidth of broadcaster. Feel free to use it in your flash applications, just hit me when you decide.

Core classes and files:

P2PMulticast application


P2PMulticast scheme

P2PMulticast scheme

sk.yoz.net.P2PMulticast.as is core class that cares about p2p clients and dispatches important events, such as connecting peer, disconnecting peer, incomming data:

package sk.yoz.net
{
    import flash.events.EventDispatcher;
    import flash.events.TimerEvent;
    import flash.utils.Timer;
    
    import sk.yoz.events.P2PMulticastEvent;
    import sk.yoz.events.P2PMulticastPersistenceEvent;
    import sk.yoz.events.P2PMulticastStreamEvent;
    
    public class P2PMulticast extends EventDispatcher
    {
        private var stream:P2PMulticastStream;
        private var persistence:IP2PMulticastPersistence;
        
        private var clientReloadTimer:Timer;
        
        public function P2PMulticast(persistence:IP2PMulticastPersistence, developerKey:String = "", 
            publishName:String = "", maxPeerConnections:uint = 0, handshakeURL:String = "", 
            clientConnectionTimeout:uint = 0)
        {
            super();
            
            stream = new P2PMulticastStream(developerKey, publishName, maxPeerConnections, 
                handshakeURL, clientConnectionTimeout);
            stream.addEventListener(P2PMulticastStreamEvent.CLIENT_ID_ASSIGNED, clientIdAssignedHandler);
            stream.addEventListener(P2PMulticastStreamEvent.PEER_CONNECTED, peerConnectedHandler);
            stream.addEventListener(P2PMulticastStreamEvent.PEER_DISCONNECTED, peerDisconnectedHandler);
            stream.addEventListener(P2PMulticastStreamEvent.PEER_DATA, peerDataHandler);
            
            this.persistence = persistence;
            persistence.addEventListener(P2PMulticastPersistenceEvent.CLIENTS_DEFINED, clientsDefinedHandler);
        }
        
        public function get connected():Boolean
        {
            return stream.connected;
        }
        
        public function set debug(value:Boolean):void
        {
            if(stream)
                stream.debug = value;
        }
        
        public function get onlineClients():Array
        {
            return stream.onlineClients;
        }
        
        public function broadcast(data:Object):Boolean
        {
            return stream.broadcast(data);
        }
        
        private function clientIdAssignedHandler(event:P2PMulticastStreamEvent):void
        {
            persistence.getClientList();
            dispatchEvent(new P2PMulticastEvent(P2PMulticastEvent.STREAM_CONNECTED));
        }
        
        private function clientsDefinedHandler(event:P2PMulticastPersistenceEvent):void
        {
            var list:Array = event.data.list;
            for each(var farID:String in list)
                stream.connectClient(farID);
            if(list.indexOf(stream.nearID) == -1)
                list.push(stream.nearID);
            persistence.saveClientList(list);
            clientReloadTimer = new Timer(10000 + Math.random() * 5000, 1);
            clientReloadTimer.addEventListener(TimerEvent.TIMER_COMPLETE, clientReloadTimerComplete);
            clientReloadTimer.start();
        }
        
        private function clientReloadTimerComplete(event:TimerEvent):void
        {
            persistence.getClientList();
            
            var timer:Timer = event.target as Timer;
            timer.removeEventListener(TimerEvent.TIMER_COMPLETE, clientReloadTimerComplete);
            timer = null;
        }
        
        private function peerDataHandler(event:P2PMulticastStreamEvent):void
        {
            dispatchEvent(new P2PMulticastEvent(P2PMulticastEvent.PEER_DATA, false, false, event.data));
        }
        
        private function peerConnectedHandler(event:P2PMulticastStreamEvent):void
        {
            var farID:String = event.data.farID;
            verifyClient(farID);
            dispatchEvent(new P2PMulticastEvent(P2PMulticastEvent.PEER_CONNECTED, false, true, event.data));
        }
        
        private function peerDisconnectedHandler(event:P2PMulticastStreamEvent):void
        {
            var farID:String = event.data.farID;
            verifyClient(farID);
            dispatchEvent(new P2PMulticastEvent(P2PMulticastEvent.PEER_DISCONNECTED, false, false, event.data));
        }
        
        private function verifyClient(farID:String):void
        {
            if(!stream.allConnected)
                return;
            var list:Array = stream.getClientList();
            list.push(stream.nearID);
            persistence.saveClientList(list);
        }
    }
}

sk.yoz.net.P2PMulticastPersistence.as class works as database of online clients. Before peer connection can be established, clients need to know each other ID. This class works as persistence where all active IDs are stored. This one is based on .xml (data) and .php (processor), but you can create your own persistence lets say based on database…:

package sk.yoz.net
{
    import com.adobe.crypto.MD5;
    
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.HTTPStatusEvent;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.SecurityErrorEvent;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    import flash.net.URLRequestMethod;
    import flash.utils.ByteArray;
    
    import sk.yoz.Log;
    import sk.yoz.events.P2PMulticastPersistenceEvent;
    
    public class P2PMulticastPersistence extends EventDispatcher implements IP2PMulticastPersistence
    {
        private var signSalt:String = "StratusIsCoool";
        
        private var loader:URLLoader = new URLLoader();
        public var scriptURL:String = "";   // http://mydomain/script.php? or ...
                                            // http://mydomain/script.php?id=123
                                            // "&sign={HASH}" is added
        
        public var debug:Boolean = false;
        private var log:Log = Log.instance;
        
        public function P2PMulticastPersistence(scriptURL:String, signSalt:String = "")
        {
            super();
            this.scriptURL = scriptURL;
            this.signSalt = signSalt;
            loader.addEventListener(Event.COMPLETE, loaderComplete);
            loader.addEventListener(Event.OPEN, openHandler);
            loader.addEventListener(ProgressEvent.PROGRESS, progressHandler);
            loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
            loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
            loader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
        }
        
        public function saveClientList(list:Array):void
        {
            var data:String = listToMessage(list);
            var sign:String = signData(data);
            var request:URLRequest = new URLRequest(scriptURL + "&sign=" + sign);
            var saver:URLLoader = new URLLoader();
            request.contentType = "text/xml";
            request.data = data;
            request.method = URLRequestMethod.POST;
            saver.load(request);
        }
        
        public function getClientList():void
        {
            var request:URLRequest = new URLRequest(scriptURL);
            request.method = URLRequestMethod.GET;
            
            try {
                loader.load(request);
            } catch (error:Error) {
                if(debug)
                    log.write("P2PMulticastPersistence.getClientList() load() Error");
            }
        }
        
        private function loaderComplete(event:Event):void
        {
            var list:Array = messageToList(loader.data);
            dispatchEvent(new P2PMulticastPersistenceEvent(P2PMulticastPersistenceEvent.CLIENTS_DEFINED, true, true, {list:list}));
        }
        
        private function messageToList(message:String):Array
        {
            var xml:XML = XML(message);
            var list:Array = [];
            for each(var id:String in xml.client.@['id'])
                list.push(id);
            return list;
        }
        
        private function listToMessage(list:Array):String
        {
            var xml:XML = <clients />
            var xmlNode:XML;
            for each(var id:String in list)
            {
                xmlNode = <client />;
                xmlNode.@id = id;
                xml.appendChild(xmlNode);
            }
            return xml.toXMLString();
        }
        
        private function openHandler(event:Event):void
        {
            if(debug)
                log.write("P2PMulticastPersistence.openHandler(" + event.toString() + ")");
        }
        
        private function progressHandler(event:ProgressEvent):void
        {
            if(debug)
                log.write("P2PMulticastPersistence.progressHandler() " + 
                        "// loaded:" + event.bytesLoaded + " total: " + event.bytesTotal);
        }
        
        private function securityErrorHandler(event:SecurityErrorEvent):void
        {
            if(debug)
                log.write("P2PMulticastPersistence.securityErrorHandler(" + event.toString() + ")");
        }
        
        private function httpStatusHandler(event:HTTPStatusEvent):void
        {
            if(debug)
                log.write("P2PMulticastPersistence.httpStatusHandler(" + event.toString() + ")");
        }
        
        private function ioErrorHandler(event:IOErrorEvent):void
        {
            if(debug)
                log.write("P2PMulticastPersistence.ioErrorHandler(" + event.toString() + ")");
        }
        
        private function signData(data:String):String
        {
            return MD5.hash(signSalt + MD5.hash(data));
        }
    }
}

sk.yoz.net.P2PMulticastStream.as class provides stratus handshake (assigned ID) and directly communicates via NetStream

package sk.yoz.net</p>
{
    import flash.events.AsyncErrorEvent;
    import flash.events.EventDispatcher;
    import flash.events.IOErrorEvent;
    import flash.events.NetStatusEvent;
    import flash.events.SecurityErrorEvent;
    import flash.events.TimerEvent;
    import flash.net.NetConnection;
    import flash.net.NetStream;
    
    import sk.yoz.utils.DataTimer;
    import sk.yoz.Log;
    import sk.yoz.events.P2PMulticastStreamEvent;
    
    public class P2PMulticastStream extends EventDispatcher
    {
        private var publishName:String = "com";
        private var maxPeerConnections:uint = 100;
        private var handshakeURL:String = "rtmfp://stratus.adobe.com";   // eg: rtmfp://stratus.adobe.com
        private var developerKey:String = "";
        private var clientConnectionTimeout:uint = 15000;
        
        private var stratus:NetConnection;
        private var publish:NetStream;
        private var clients:Object = {};
        
        public var debug:Boolean = false;
        private var log:Log = Log.instance;
        
        public function P2PMulticastStream(developerKey:String = "", publishName:String = "", 
            maxPeerConnections:uint = 0, handshakeURL:String = "", clientConnectionTimeout:uint = 0)
        {
            super();
            if(publishName)
                this.publishName = publishName;
            if(maxPeerConnections)
                this.maxPeerConnections = maxPeerConnections;
            if(handshakeURL)
                this.handshakeURL = handshakeURL;
            if(developerKey)
                this.developerKey = developerKey;
            if(clientConnectionTimeout)
                this.clientConnectionTimeout = clientConnectionTimeout;
                
            stratus = new NetConnection();
            stratus.maxPeerConnections = this.maxPeerConnections;
            stratus.addEventListener(NetStatusEvent.NET_STATUS, stratusStatusHandler);
            stratus.addEventListener(AsyncErrorEvent.ASYNC_ERROR, stratusAsyncErrorHandler);
            stratus.addEventListener(IOErrorEvent.IO_ERROR, stratusIOErrorHandler);
            stratus.addEventListener(SecurityErrorEvent.SECURITY_ERROR, stratusSecurityErrorHandler);
            stratus.connect(this.handshakeURL + "/" + this.developerKey + "/");
        }
        
        public function get connected():Boolean
        {
            return nearID ? true : false;
        }
        
        public function get onlineClients():Array
        {
            var list:Array = [];
            for(var farID:String in clients)
                if(client(farID).connected)
                    list.push(farID);
            return list;
        }
        
        public function get allConnected():Boolean
        {
            for(var farID:String in clients)
                if(!client(farID).connected)
                    return false;
            return true;
        }
        
        private function initPublish():void
        {
            if(debug)
                log.write("P2PMulticastStream.initPublish()");
            publish = new NetStream(stratus, NetStream.DIRECT_CONNECTIONS);
            publish.addEventListener(NetStatusEvent.NET_STATUS, publishStatusHandler);
            publish.addEventListener(AsyncErrorEvent.ASYNC_ERROR, publishAsyncErrorHandler);
            publish.addEventListener(IOErrorEvent.IO_ERROR, publishIOErrorHandler);
            publish.publish(publishName);
            publish.client = {
                onPeerConnect: function(caller:NetStream):Boolean
                {
                    connectClient(caller.farID);
                    return true;
                }
            };
        }
        
        public function get nearID():String
        {
            return stratus && stratus.connected ? stratus.nearID : '';
        }
        
        private function client(farID:String):StratusStream
        {
            return clients.hasOwnProperty(farID) ? clients[farID] : null;
        }
        
        public function connectClient(farID:String):void
        {
            if(farID == nearID || clients.hasOwnProperty(farID))
                return;
                
            if(debug)
                log.write("P2PMulticastStream.connectClient(" + farID + ")");
            var c:StratusStream = new StratusStream(stratus, farID);
            c.addEventListener(NetStatusEvent.NET_STATUS, clientStatusHandler);
            c.play(publishName);
            c.client = {
                onData: function(data:Object):void
                {
                    if(debug)
                        log.write("P2PMulticastStream.client(" + farID + ").onData(" + data.toString() + ")");
                    dispatchEvent(new P2PMulticastStreamEvent(P2PMulticastStreamEvent.PEER_DATA, false, false, {farID:farID, data:data}));
                }
            };
            clients[farID] = c;
            
            var timer:DataTimer = new DataTimer(clientConnectionTimeout, 1, {farID:farID});
            timer.addEventListener(TimerEvent.TIMER_COMPLETE, clientConnectionTimeoutHandler);
            timer.start();
        }
        
        private function clientConnectionTimeoutHandler(event:TimerEvent):void
        {
            var timer:DataTimer = event.target as DataTimer;
            var farID:String = timer.data.farID;
            if(client(farID) && !client(farID).connected)
                disconnectClient(farID);
            timer.removeEventListener(TimerEvent.TIMER_COMPLETE, clientConnectionTimeoutHandler);
            timer = null;
        }
        
        private function disconnectClient(farID:String):void
        {
            if(farID == nearID || !client(farID))
                return;
            clients[farID] = null;
            delete clients[farID];
            if(debug)
                log.write("P2PMulticastStream.disconnectClient(" + farID + ")");
            dispatchEvent(new P2PMulticastStreamEvent(P2PMulticastStreamEvent.PEER_DISCONNECTED, true, true, {farID:farID}));
        }
        
        public function broadcast(data:Object):Boolean
        {
            if(!connected)
                return false;
            try
            {
                publish.send("onData", data);
            }
            catch(error:Error)
            {
                if(debug)
                    log.write("P2PMulticastStream.sendData(" + data.toString() + ") // ERROR: " + error.message);
                return false;
            }
            return true;
        }
        
        private function clientStatusHandler(event:NetStatusEvent):void
        {
            var farID:String = event.target.farID;
            if(debug)
                log.write("P2PMulticastStream.clientStatusHandler(" + event.info.code + ")");
            switch(event.info.code)
            {
                case "NetStream.Play.Start":
                    if(debug)
                        log.write("P2PMulticastStream.clientStatusHandler(" + event.info.code + ") " + 
                                "// client(" + farID + ") connected");
                    client(farID).connected = true;
                    dispatchEvent(new P2PMulticastStreamEvent(P2PMulticastStreamEvent.PEER_CONNECTED, true, true, {farID:farID}));
                    break;
            }
        }
        
        private function stratusStatusHandler(event:NetStatusEvent):void
        {
            if(debug)
                log.write("P2PMulticastStream.stratusStatusHandler(" + event.info.code + ")");
            switch(event.info.code)
            {
                case "NetConnection.Connect.Success":
                    if(debug)
                        log.write("P2PMulticastStream.stratusStatusHandler(" + event.info.code + ") " + 
                                "// p2p connection ready (" + nearID + ")");
                    initPublish();
                    dispatchEvent(new P2PMulticastStreamEvent(P2PMulticastStreamEvent.CLIENT_ID_ASSIGNED, true, true, {nearID:nearID}));
                    break;
                    
                case "NetStream.Connect.Closed":
                    if(debug)
                        log.write("P2PMulticastStream.stratusStatusHandler(" + event.info.code + ") " + 
                                "// p2p disconnected (" + event.info.stream.farID + ")");
                    disconnectClient(event.info.stream.farID);
                    break;
            }
        }
        
        private function stratusAsyncErrorHandler(event:AsyncErrorEvent):void
        {
            if(debug)
                log.write("P2PMulticastStream.stratusAsyncErrorHandler(" + event.text + ")");
        }
        
        private function stratusIOErrorHandler(event:IOErrorEvent):void
        {
            if(debug)
                log.write("P2PMulticastStream.stratusIOErrorHandler(" + event.text + ")");
        }
        
        private function stratusSecurityErrorHandler(event:SecurityErrorEvent):void
        {
            if(debug)
                log.write("P2PMulticastStream.stratusSecurityErrorHandler(" + event.text + ")");
        }
        
        private function publishStatusHandler(event:NetStatusEvent):void
        {
            if(debug)
                log.write("P2PMulticastStream.publishStatusHandler(" + event.info.code + ")");
            switch(event.info.code)
            {
                case "NetStream.Publish.Start":
                    if(debug)
                        log.write("P2PMulticastStream.publishStatusHandler(" + event.info.code + ") " + 
                                "// publishing started (" + nearID + ")");
                    break;
            }
        }
        
        private function publishAsyncErrorHandler(event:AsyncErrorEvent):void
        {
            if(debug)
                log.write("P2PMulticastStream.publishAsyncErrorHandler(" + event.text + ")");
        }
        
        private function publishIOErrorHandler(event:IOErrorEvent):void
        {
            if(debug)
                log.write("P2PMulticastStream.publishIOErrorHandler(" + event.text + ")");
        }
        
        public function getClientList():Array
        {
            var list:Array = [];
            for(var farID:String in clients)
                list.push(farID);
            return list;
        }
    }
}

Application.mxml is demo application, export it and open in 2 (or more) different windows. When connected (online text appear), you can communicate to other instances via p2p. Do not forget to insert your stratus developer key:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
    applicationComplete="init()" viewSourceURL="srcview/index.html">
<mx:Script>
<![CDATA[
    import sk.yoz.events.P2PMulticastEvent;
    import sk.yoz.net.P2PMulticastPersistence;
    import sk.yoz.net.P2PMulticast;
    import sk.yoz.Log;
    
    private var log:Log = Log.instance;
    private var p2p:P2PMulticast;
    
    private function init():void
    {
        log.output = logArea;
        
        var scriptURL:String = "http://localhost/p2p/clients.php?";
        var signSalt:String = "StratusIsCoool";
        var developerKey:String = "***"; // HERE COMES YOUR STRATUS DEVELOPER KEY
        
        var persistence:P2PMulticastPersistence = new P2PMulticastPersistence(scriptURL, signSalt);
        p2p = new P2PMulticast(persistence, developerKey);
        p2p.debug = true;
        persistence.debug = true;
        
        p2p.addEventListener(P2PMulticastEvent.STREAM_CONNECTED, streamConnectedHandler);
        p2p.addEventListener(P2PMulticastEvent.PEER_CONNECTED, peerChangeHandler);
        p2p.addEventListener(P2PMulticastEvent.PEER_DISCONNECTED, peerChangeHandler);
        p2p.addEventListener(P2PMulticastEvent.PEER_DATA, peerDataHandler);
    }
    
    private function streamConnectedHandler(event:P2PMulticastEvent):void
    {
        info.text = "online";
    }
    
    private function peerChangeHandler(event:P2PMulticastEvent):void
    {
        peers.dataProvider = p2p.onlineClients;
    }
    
    private function peerDataHandler(event:P2PMulticastEvent):void
    {
        dataArea.text += event.data.farID + ":" + event.data.data;
    }
    
    private function broadcast():void
    {
        p2p.broadcast(message.text);
        callLater(function():void
        {
            message.text = "";
        });
    }
]]>
</mx:Script>
<mx:HBox width="100%" height="100%">
    <mx:VBox width="100%" height="100%">
        <mx:TextArea id="logArea" width="100%" height="100%" />
        <mx:TextArea id="dataArea" width="100%" height="100%" />
        <mx:HBox width="100%">
            <mx:TextInput id="message" width="100%" enter="broadcast()"/>
            <mx:Button click="broadcast()" label="send"/>
        </mx:HBox>
    </mx:VBox>
    
    <mx:VBox width="400">
        <mx:Text id="info" text="offline"/>
        <mx:List id="peers" width="100%"/>
    </mx:VBox>
</mx:HBox>
</mx:Application>

clients.php file reads and writes clients.xml. It uses hash to validate data. Make sure you set sign salt “StratusIsCoool” in .php file same as signSalt in Application.mxml

<?php
function signData($data){
	return md5("StratusIsCoool".md5($data));
}

function defaultize(&$key, $default=null){
   return is_null($key)?$key=$default:$key;
}

$file=dirname(__FILE__)."/clients.xml";
$bytes=defaultize($GLOBALS['HTTP_RAW_POST_DATA'], null);
$sign=defaultize($_GET['sign'], null);

if($bytes){
	if(!$sign || $sign !== signData($bytes))
		exit;
	
	$fd=fopen($file, 'w');
	fwrite($fd, $bytes);
	fclose($fd);
	exit;
}

if(is_file($file))
	readfile($file);

Other required files can be found here: P2PMulticast source. Don not forget to export your application into Flash Player 10. P2P Multicast is used in onBoard collaborative painting.


Viewing all articles
Browse latest Browse all 2

Latest Images

Trending Articles





Latest Images