viernes, 23 de octubre de 2009

P2P en Flex... Gracias a Stratus

Introduction

Peer to Peer (P2P) applications have been around for quite some time now. Adobe Stratus, a recently released service from Adobe in combination with Flash Player 10 provides developers all there is needed to create P2P applications today. In this article, we will cover the following:

* What is Adobe Stratus
* How to get started with Adobe Stratus Service and understanding the Flash Player 10 API classes : NetConnection and NetStream vis-à-vis P2P requirement
* Write a P2P Application (Employee Review Application) using Adobe Stratus and the Flash Player 10 NetConnection and NetStream classes.

What is Adobe Stratus?

With Flash Player 10, Adobe brought in enhancements that make writing P2P applications a reality. Flash Player 10 provides support now for a new protocol called Real Time Message Flow Protocol (RTMFP). This protocol is well suited to creating applications that need to communicate to each other directly. This communication is facilitated via the Adobe Stratus Service which supports the RTMFP protocol and is a rendezvous Hosted Service that facilitates connection between two Flash Player 10 instances.

Once the Flash Player 10 instances are connected to each other, the two applications can continue to talk to each other without going through an intermediate server side.

Adobe has had Flash Media Server (FMS) over the years and recently they have also introduced Adobe Flash Collaboration Services (AFCS), formerly known as Cocomo. FMS currently does not support the RTMFP protocol but future versions of it could. On the other hand, Adobe Stratus in conjunction with the Flash Player 10 APIs does not provide support for full P2P capabilities like relay, shared data models, etc. but depending on the kind of application that you may require, it might just about the fit the requirements of your P2P Application without requiring additional server side architecture.
Getting started with Adobe Stratus

In this section, we will go through all the steps that you need to do in order to setup yourself to develop Adobe Stratus based P2P applications. Along with getting a developer key, setting up your Project settings and understanding the basic classes, we will cover just about enough to help you dive into code.
1. Get a Developer Key

To get started with the Adobe Stratus, you will need to get an Adobe Stratus Developer Key. Visit the Adobe Stratus home page located at http://labs.adobe.com/technologies/stratus/ and click on the link shown below to get your developer key. All you need is to click on the link and register with your Adobe ID.

2. Flash Player and Flex Builder Project Settings

* Flash Player 10 is a must to make use of the P2P features, so make sure that you have Flash Player 10 installed on your system.
* You need to also have Flex Builder 3.0.2 or above. In case you have an older version of Flex Builder 3, you can refer to a detailed article at http://blog.everythingflex.com/2008/05/20/using-flash-player-10-within-flex-builder that will guide you through the steps.
* To compile your project and target Flash Player 10, go to Flex Project Properties and set the value for the Flash Player version to 10.0.0 as shown below. If there is a Flash Player 9 version out there, please change it to the one shown below:

Alt Text
3. Understand the Flash Player 10 classes: NetConnection and NetStream

There are a couple of classes that we need to understand from the flash.net package. They are NetConnection and NetStream. While this is not a tutorial to cover all the details about these classes, we will cover enough here to get going with P2P. Readers looking for more detail should look at the Flash Player 10 documentation for these classes. We have provided the links for that in the Resources section.
NetConnection

* The NetConnection class allows for bi-directional connection between two Flash Player 10 or AIR application instances.
* The first step that you will need to do in your application is to create an instance of the NetConnection object.
* Once you create the instance, you need to call the connect() method on the NetConnection class to connect to the Adobe Stratus Server. The connect() method needs to be provided a single parameter that will contain the URI of the Adobe Stratus Server. The format of the URI is currently required as rtmfp://stratus.adobe.com/DEVELOPER_KEY, where DEVELOPER_KEY is your key for using the Adobe Stratus Service.
* We can listen to NetStatusEvent.NET_STATUS event on the NetConnection object. Several information codes are returned in the NET_STATUS event, primarily NetConnection.Connect.Success (if all is well) and NetConnection.Connect.Failed(if connection has failed due to reasons like incorrect developer key, network issue, etc).
* Once a connection is successfully established i.e. NetConnection.Connect.Success, we can retrieve our Peer Id (a unique 256-bit key) using the nearID attribute. Then we can start sending and receiving messages from other Flash Player 10 instances. This is done through the NetStream class that we cover next.

NetStream

* The NetStream class allows for one-way streaming connection between two Flash Player 10 or AIR application instances. The NetStream object is a channel that you have created within a NetConnection object.
* To publish a channel, call the publish() method of the NetStream class. The publish method takes a single parameter i.e. the channel name.
* To subscribe to a published stream and receive data, using NetStream.play(). The play() method takes a single parameter i.e. the channel name.
* To send a message to another Flash Player instance, send() method to send text messages to all subscribed clients. The send() method takes two parameters, the handler name (callback method) that will be invoked on receiving the message. The second parameter is the message that is sent.

4. Basic Flow of P2P communication using Stratus and NetConnection/NetStream

High Level steps that allow an application to engage in P2P communication using Stratus and NetConnection/NetStream classes is given below:

1. Create a NetConnection object and connect to the Adobe Stratus using the connect() method.
2. On successful connect to the Adobe Stratus Service, create a publishing stream (NetStream instance) and start the publishing stream by invoking the publish() method
3. To connect to a publishing stream i.e. to subscribe to a stream being published, create a receiving stream (another NetStream instance) by using the Peer ID of the application you wish to connect to.
4. To start receiving the stream, invoke the play() method on the receiving Stream created above.
5. To send messages to another application, invoke the send() method on the publishing stream.
6. Implement callback methods that will be invoked when a message is received from a Peer (Note that Peer used the send() method in the above step). The callback method will most likely contain your Application Logic i.e. actions that needs to be executed on receiving a message.

Sample P2P Application : Employee Review

The Employee Review application is a simple proof of concept application to apply what we have covered so far. It is not a full proof application and is used to simply demonstrate the power of Adobe Stratus and P2P features of Flash Player and how you can achieve that with few lines of code.

The Employee Review application is an application where a manager asks an employee for his feedback on employees. Once the initial connections and Peer Ids are exchanged, the Manager sends across the employee detail to the Peer. The Peer sees the Employee Record and fills out a simple form giving his rating for the particular employee in several areas. Once he has completed this, he sends the data across to the Manager who receives it. Except for the initial process of getting a Peer Id, the rest of the operations are happening directly between the two Flash Player instances.

Let us first understand how to run the application. Remember that for any real application, you will need an external service that will not only validate the users and their roles but also maintain the Peer Ids and allow them to view which Peers are online, etc. For the purposes of this article, we will keep it simple and directly feed in the Peer Ids that we wish to connect to.

To run the application, run two instances of the Flex Application. Shown below is the first instance:
Alt Text

Let us make this instance the Manager. Enter admin as the user name and click on Connect. This will connect to the Adobe Stratus Service and get a Peer ID. On successful connection, the Peer ID obtained is highlighted below for your reference:
Alt Text

Launch another instance of the Application. This time enter any name other than admin (enter 'user') in the username field. Note that we are intentionally doing this here to keep things simple but in the real world, you will need an authentication service. Click on Connect, you will see the user getting a Peer ID too as shown below:
Alt Text

The next step will be to connect each of them to the other. To do this, simply paste the My Peer Id of the other in the Other Peer Id field and click on Peer Connect button. Do this for both the applications, each time copying and pasting the Peer Id of the other application into the current applications' Other Peer Id field. The screenshots for the admin and user application are shown below respectively:

This is the Admin application screen:
Alt Text

And this is the User application screen:
Alt Text

You will see a common chat window for both the applications, where you can enter some messages and see it appearing on the other side. The admin user will have an Employee List, whereas the user will have a review form that is currently empty and not populated with any employee data.

To do the review, follow these steps:

1. For the admin user, click on the Load Employee Data button. This will populate the grid with some dummy employee data. Ideally this is fetched too from an external system.
Alt Text
2. Select any Employee Record and click on Send Review Form button. This will send the Employee Record data across to the other Peer. In our case, we selected the first Record (Jack Sparrow) and clicked on the Send Review Form button.
3. Switch to the user application, you will see that the Employee Details are populated. You can now select ratings for different performance criteria, give any additional comments and click on Send button.
Alt Text
4. Switch to the admin application, you will find that the completed review has reached the admin application with the summary of details. The grid is also updated for the Rating column with the cumulative ratings provided by the peer.
Alt Text

All this action has happened directly between the Flash Player 10 instances without any intermediate server layer. The only server layer is the connection to the Adobe Stratus Service for the Peer Id. The connection to the Stratus Service is maintained by the application via the NetConnection classes internally for you.

Now, that you saw how it works, let us look at the code by visiting the key areas:

1. Connecting to the Stratus Service and starting to publish a stream

The first step that we need to do is to make a connection to the Adobe Stratus Service and get our Peer ID. The Peer Id is a unique 256-bit key that is provided to you. To connect to the Adobe Stratus Service, we need a couple of things: the RTMFP endpoint of the Adobe Stratus Service and your Developer Key. Shown below the function that is invoked when you click on the Connect button.


private function connectToStratus(event:Event):void {
netConnection = new NetConnection();
netConnection.addEventListener(NetStatusEvent.NET_STATUS,onStratusConnect);
netConnection.connect(StratusAddress + "/" + DeveloperKey);
}

As listed above, we create an instance of the flash.net.NetConnection object using the no-args constructor. We can trap events from the NetConnection object by listening for the NetStatusEvent.NET_STATUS events, which we will see in a while. We then connect to the service using the connect method, where we provide the RTMFP end point and our Developer Key. The format is as follows: rtmfp://stratus.adobe.com/Your_Developer_Key. The Adobe Stratus Service address is defined in our source code file as follows:

private const StratusAddress:String = rtmfp://stratus.adobe.com;

Since we are listening to the NetStatusEvent.NET_STATUS event, we can get notified if the connection was successful or not. If the connection is successful, we get a NetConnection.Connect.Success code as shown in the code below. There are other codes returned too like NetConnection.Connect.Failed if there was a problem due to network connectivity, incorrect developer key, etc.


private function onStratusConnect(event:NetStatusEvent):void {
trace(event.info.code);
if (event.info.code == "NetConnection.Connect.Success") {
txtMyPeerId.text = netConnection.nearID;

//Create a NetStream for sending
sendStream = new NetStream(netConnection,NetStream.DIRECT_CONNECTIONS);
sendStream.addEventListener(NetStatusEvent.NET_STATUS,onSendStreamHandler);
sendStream.publish("mystream");
}
}

In the code above, we do some important stuff.
* If the connection is successful, the Stratus Service returns us a unique 256-bit key, which is our Peer Id. Your Peer Id is populated in the nearID field of your netConnection object. This Peer Id is how other applications will connect to us. So what we are doing at this point in time is simply displaying the Peer ID in the My Peer Id text box. This is then copied manually into the other applications Other Peer Id text field as we saw when we ran the example.
* Since we are now connected to the Adobe Stratus Service and have a Peer Id, we can start publishing a stream. Remember that streams are one-way only, hence we will have one stream for publishing and another one for subscribing. In this case, we create a publishing stream called sendStream. When creating a publishing stream, all you need is the NetConnection which you pass as the first parameter to the constructor. The second parameter is specific to Flash Player 10 and it indicates that you will allow Direct connections to be made to this Flash Player instance.
* Finally, to start publishing, we call the publish method on the sendStream object. The publish method takes a channel name, which can be any name. Our channel name here is called mystream. This will be the same channel name on which the other peer will subscribe and get data.

2. Subscribing to a Stream

To get data from another Peer, we need to do the following:
1. Create a subscription stream by connecting to the Peer.
2. Subscribe to the Channel published by the Peer

Let us look at how this is done by going through the code:


private function connectToPeer(event:Event):void {
recStream = new NetStream(netConnection,txtOtherPeerId.text);
recStream.addEventListener(NetStatusEvent.NET_STATUS,onReceiveStreamHandler);
recStream.play("mystream");
recStream.client = this;
}

private function onSendStreamHandler(event:NetStatusEvent):void {
trace(event.info.code);
}

* The connectToPeer() method is called when you click on the Peer Connect button in the application. What it does first is to create a new NetStream object. This NetStream object called recStream (Receiving Stream) and is created slightly different than the publishing stream (sendStream object) that we created earlier. The first parameter of the constructor is the same i.e. it requires the NetConnection object. Since we are interested in establishing connection to the other Peer here, we need to provide the Peer Id of the other application. This value is obtained from the text field Other Peer Id, which you manually copied from the other application. In a real world application, then would be obtained from some external service.
* To start subscribing, simply call the play() method of the NetStream object. The parameter to pass in here is the channel name that we are interested in subscribing to. This channel name is the same one on which the other peer will be publishing.
* We add an Event Listener here on the Stream to catch the NET_STATUS events. If the NetStream has started playing successfully i.e. it has subscribed successfully, then it gives a NetStream.Play.Start code, in which case we simply call the showWorkArea method. This method (not listed below) shows a list of Employees if the user is admin or else it shows the Review Form.
* Finally and most importantly, we set the client attribute of the receiving stream to the current instance. This is required so that when the publishing stream sends a message to your receiving stream, it will need to know where it can find the handlers for the receiving message. This will become clear in the next section.

3. Sending messages to each other

To send a message to the Peer, you need to invoke the send() method of the NetStream class. In our case the Netstream class object that we will be using is the sendStream object that we created earlier. The send() method requires two parameters. The first one is the handler (in other words the method) that will be invoked on the receivingStream Flash Player instance. And the second is the message Object. The message object can be any class that you wish to send across.

Let us first look at the basic chat messages since that is the simplest. When you enter any chat message and click on the Send button, we call the sendChatMessage() method which is shown below. This method invokes the send() method on the sendStream. The first parameter is the name of the method i.e. the handler which will be invoked on the receiving application. The second parameter is our chat message text. So, we have an equivalent method called the receiveChatMessage() which will be invoked in the receiving application. The method will be invoked automatically and the message will be passed in its second parameter. We simply extract it out and append it to our chat window.


private function sendChatMessage():void {
sendStream.send("receiveChatMessage",txtMsg.text);
}

public function receiveChatMessage(msg:String):void {
txtChatMessages.text += "\n" + msg;
}

Now, things should get clear on how to send the review form across and receive the feedback too. The Admin user selects a particular Employee Record and clicks on the Send Review Form button. We invoke the sendReviewForm() method shown below. It simply retrieves the currently selected row from the datagrid and calls the send() method passing the handler name receiveNewReview and the selected row as the second parameter.


private function sendReviewForm():void {
var obj:Object = dgEmployees.selectedItem;
sendStream.send("receiveNewReview",obj);
}

In the handler receiveNewReview() shown below, we simply extract out the person object that has been passed and initialize the ratings fields.


public function receiveNewReview(person:Object):void {
//Set the Form
empid.text = person.empid;
fname.text = person.fname;
lname.text = person.lname;

//Initialize the Ratings
cbCustomerInteraction.selectedIndex = 0;
cbPeerFeedback.selectedIndex = 0;
cbTechKnowledge.selectedIndex = 0;
cbTimeliness.selectedIndex = 0;

//Clear the Comments if any
txtComments.text = "";
}

In the user application, once we are done with entering the review details, we click on the Send button of the Review form. This will invoke the sendCompletedReview() method as shown below. The function builds the object and calls the send() method on its sendStream. The first parameter in the send() method is the handler name receiveCompletedReview and the second parameter is the completed review object.


public function sendCompletedReview():void {
var review:Object = new Object();
review.empid = empid.text;
review.fname = fname.text;
review.lname = lname.text;
review.rating_CustomerInteraction = cbCustomerInteraction.selectedLabel;
review.rating_TechKnowledge = cbTechKnowledge.selectedLabel;
review.rating_Timeliness = cbTimeliness.selectedLabel;
review.rating_PeerFeedback = cbPeerFeedback.selectedLabel;
review.comments = txtComments.text;
sendStream.send("receiveCompletedReview",review);
}

In the handler receiveCompletainedReview() shown below, we simply extract out the completed review object that has been passed and populate the Last Review Summary TextBox along with a cumulative score for the rating column in the datagrid for the particular employee.


public function receiveCompletedReview(review:Object):void {
//Set the Last Review
txtLastReview.text = "";
txtLastReview.text = "Received Review for Emp ID: " + review.empid + " -- " + review.fname + " " + review.lname;
txtLastReview.text += "\n" + "Technical Knowledge Rating : " + review.rating_TechKnowledge;
txtLastReview.text += "\n" + "Customer Interaction Rating : " + review.rating_CustomerInteraction;
txtLastReview.text += "\n" + "Timeliness Rating : " + review.rating_Timeliness;
txtLastReview.text += "\n" + "Peer Feedback Rating : " + review.rating_PeerFeedback;
txtLastReview.text += "\n" + "Comments : " + review.comments;
var rating:Number = Number(review.rating_TechKnowledge) + Number(review.rating_CustomerInteraction) + Number(review.rating_Timeliness) + Number(review.rating_PeerFeedback);
txtLastReview.text += "\n" + "Total Score : " + rating;

//Set the Rating in the dgEmployees Grid also

for (var i:int = 0; i< dpEmployeeData.length;i++) {
var empRecord: Object = dpEmployeeData.getItemAt(i);
if (empRecord.empid == review.empid) {
empRecord.rating = rating;
break;
}
}

dpEmployeeData.refresh();
}

Additional Notes

* The entire Flex Project is available for download. To compile it successfully, you will need to replace the Developer Key in the file EmployeeReview.mxml with your own Developer Key. The link to get a Stratus Key is given in the resources section.
* The application demonstrated here is not complete in all respects vis-à-vis making sure that no more than one peer can connect, enabling/disabling buttons depending on the functionality, etc. To Accept and Reject Peer Connections, there is an onPeerConnect function that can be written by the developer which will allow or reject the Peer connection depending on your application logic. For more information, refer to the Flash Player 10 NetConnection and NetStream documentation that is listed in the references.
* Once the two Peers are connected to each other, they can communicate to each other without going through an intermediate server layer. In other words they communicate directly over the network but still need to be connected to the Adobe Stratus Service. The understanding is that this is not too expensive to the application but it is required.

Adobe Stratus Resources

* Adobe Labs page for Stratus
* Stratus Developer Key Registration
* Adobe DevNet Article : Stratus service for developing end-to-end applications using RTMFP in Flash Player
* Sample Video Phone Application from Adobe
* Flash Player 10 NetConnection class documentation
* Flash Player 10 NetStream class documentation

Source Code
StratusSample.zip

Read more from Romin Irani. Romin Irani's Atom feed iromin on Twitter

Fte: http://www.insideria.com/2009/07/getting-started-with-adobe-str.html