Building a P2P Network Vote System Using RTMFP

Flash Player 10 brought us the RTMFP protocol, then the 10.1 update added the flash.net.NetGroup granularity. What does this mean for us? Short answer: easy peer to peer communications. Not so short answer: Zero-configuration networking to exchange message between several Flash Player clients..

We will have a look at what exactly RTMFP is, how it can be use in a LAN even without using Adobe's Cirrus Server, then we will start to implement our classes to use the netGroup features. First milestone will be a very basic and simple chat client to see that messages are exchanged, and then we will go on and develop a vote application to build something more close to real-world applications.

Final Result Preview

Let's take a look at the final result we will be working towards. Simply open this flash application several times (within multiple browsers, or multiple browser tabs, or multiple computers on the same LAN). You'll be able to have a single vote manager in the group, and as soon as a manager is set the others will automatically be set to clients. Then the manager can edit a question and its answers, then submit it to the group. The results will update in real time, and then will be displayed by all the computers/browsers of the group. Keep in mind that no network configuration has to be done, no server is involved. I guess you start to see the potential of these features for LAN applications interconnecting several computers.

Step 1: Real Time Media Flow Protocol?

RTMFP stands for Real Time Media Flow Protocol. This network protocol has been developed by Adobe and made public in Flash Player 10 and AIR 1.5. To paraphrase Adobe's Labs page, is a low latency protocol with peer-to-peer capabilities (amongst others). We won't go deep in details here about how it works, because everything is described in the Cirrus page on the Adobe Labs page.

This protocol enables automatic service discovery, which is quite like Apple's Bonjour protocol. From a Flash developer's point-of-view, it's a complicate stuff under the hood that enables automatic discovery of other clients of our 'service' in the network. Then our Flash clients will send messages (data or media) directly to each others. In Flash Player 10.0 these connections were straightforward: the publishing peer was sending the data to every other peer. Now in 10.1 we have multicast and every peer might automatically act as a relay to dispatch the data to the next peer.

What is multicast? In traditional network communications we often work in unicast mode, like HTTP request, a message between two actors over the network. To send data to several targets, you might first think of unicast broadcasting, which is sending the data for each target. If you have n targets, the data is send n times. Quite expensive. Then we might think about broadcast. To make it very simple, broadcast is sending the data to every node of the network. That means we are also sending it to computers which aren't interested by this message. It might do the job, but it does create extra bandwidth usage to network nodes which are not interested by our data. Then came the multicast, which is the kind of the way to transmit data which is used in RTMFP. Multicast send one single message, then the network nodes will take care of its distribution and replication when necessary. This is a very short summary, and Wikipedia's routing page does a great job for going further into this.

What's important for us is to know that something complicated and optimized is done under the hood, and that we'll need a valid multicast address. So what is a valid multicast address? The range of IP addresses we might use has been set by the IANA. For IPv4, we might choose any one in the 224.0.0.0 to 239.255.255.255 range, but some addresses has been reserved: all the 224.0.0.0 to 224.0.0.255 should not be used. But some addresses outside this range are also reserved. To get the official list you just have to look at IANA's official list to see if the address you're planning to use is reserved. In the next steps we will use 239.252.252.1 as our multicast address. Knowing if your address is a valid multicast one might be helpful for you to debug your project, as a common mistake is to use traditional unicast address like 192.168.0.3. To prepare yourself to the future, valid multicast addresses when you'll be using IPv6 must be in the range ff00::/8.

One final note: Adobe's Cirrus Server enable peer discovery outside of your LAN (i.e. across the Internet). To be able to use Cirrus server, you just have to register to have a Cirrus developer key. When using RTMFP without Cirrus server, we'll have all the peer-to-peer features, but limited to the local network.

Step 2: OK, Now I Want to See Some Code!

So Flash Player now supports RTMFP. Great. But how do we use it? The connection magic happens in the flash.net.NetConnection class. And the group support added by Flash Player 10.1 is done through the flash.net.NetGroup class.

In the next steps we will look at the connection process and which keywords are involved, and then we will merge this together. So let's start by listing what we will need to make a connection:

Store an Instance of NetConnection

Obviously we need an instance of NetConnection, which is included in version 4.1 of the Flex SDK. This NetConnection object exposes a connect() public method. According to this method documentation, connect() waits for a RTMFP URL to create peer-to-peer connection and IP Multicast communication. For a simple LAN peer-to-peer discovery, using "rtmfp:" is enough. For using connect with the Cirrus server, we just have to add the server and devkey strings to this address. This give a really simple connect method in our manager:

/**
* Using a serverless rtmfp does work for local subnet peer discovery.
* For internet usage with Cirrus server, something like
* rtmfp://cirrus.adobe.com/"; will be more useful
*/
public var _rtmfpServer:String = "rtmfp:";
/**
* If you are using a Cirrus server, you should add your developer key here
*/
public var _cirrusDevKey:String = "";
protected var netConnection:NetConnection = null;
public function connect():void
{
netConnection = new NetConnection();
netConnection.addEventListener(NetStatusEvent.NET_STATUS, netConnection_netStatusHandler, false,0,true);
netConnection.connect(_rtmfpServer + _cirrusDevKey);
}

I am assuming throughout this tutorial that you will import the necessary classes yourself, or that your IDE will do it for you.

Once we have called the netConnection.connect() method, we have to wait and listen to a NetStatusEvent.NET_STATUS event with its info.code property of "NetConnection.Connect.Success". This success info tell us the netConnection is ready, so we will be able to create a netGroup in our netConnection_onConnectSuccess() method.

Creating the NetGroup

The netGroup will be the 'lobby' in which our peers communicate. Therefore it needs to be specified. That's what the flash.net.GroupSpecifier class is intended for. Its constructor waits for a group name which will identify our group. We will compose this name with an applicationName and a groupName suffix. This way it will be easy to create several groups in the same applications without modifying the netGroup manager we are creating. The netGroup class exposes several useful parameters:

multicastEnabled, which is used for streams (when used with a netStream.play() or netStream.publish() methods). In our case we won't work with media, but I still keep this property here to because it is very useful for media; it's false by default.

objectReplicationEnabled is a Boolean - false by default - which specifies if the replication is enabled. For sending simple message that might be exactly the same but at different times (like a notification for a new occurrence of the same event), I found very useful to explicitly set it to true, otherwise some peers might be a little too smart and would think they already dispatched this messages, and therefore will not replicate it to the next peer, so that not every peer of the group get the new occurrence of the message.

postingEnabled, false by default, set if posting is allowed in the group. Obviously we will set it to true, as every peer will send its update via this mechanism.

routingEnabled enables direct routing in the netGroup.

ipMulticastMemberUpdatesEnabled is a flag that enables IP multicast sockets. This improves P2P performance in a LAN context. As our example will be LAN-based, we will obviously set it to true.

serverChannelEnabled help peer discovery by creating a channel on the Cirrus server. As we won't use Cirrus in our example, we'll ignore it by now.

Once we've set all these properties to match our need, we set the multicast address we'll use to interconnect data in our group. This is done by calling the groupSpecifier.addIPMulticastAddress("239.252.252.1:20000") method. Now everything is set-up, we just have to create an instance of NetGroup with this parameter to make the group connection happens:

Then the netGroup dispatch a NetStatusEvent.NET_STATUS event with its code property of "NetGroup.Connect.Success" when it's ready to be used. By now we can start using the netGroup.post(object) method to send messages to our peers.

We've talked a little bit about the NetStatusEvent which give us the connection success info. But it's also useful for retrieving everything happening to our netConnection or netGroup. Therefore we will add some values in a switch/case tree to get the relevant values of the NetStatusEvent for our netConnection:

Our netGroup's netStatusEvent listener will have similar tests to listen to group-specific actions: messages posted, peer (or neighbor in the netGroup API) joined or left, so we will have this simple routing method (some properties there will be implemented later, like or custom NetGroupEvent class, our VoNetGroupMessage, but you will have a look at the codes received, and you can note that we already have a direct count of currently connected peers by reading netGroup.neighborCount.

We have highlighted the main keywords and methods to use a NetGroup. In the next step we'll formalize this quite a bit, writing our own NetGroupManager class to simplify this API for a basic message-exchange usage.

Step 3: Building our own NetGroupManager Utilities

Because we don't want to worry about the inner working once it has been done, we will build a utility class to handle of the network communication. This manager will use a NetGroupEvent to dispatch its update, and a NetGroupMessage object to unify the structure and get some helpers. This will give us a generic manager, written once, ready to be used in any project.

First, as a debug tool, we'll create a little helper. In Flash Builder, create a new Flex Library Project, name it LibHelpers, create a new 'helpers' package and paste this DemoHelper class in this package:

It give us something more useful than a simple trace(). Calling DemoHelper.put('some string'); will output the message, with a timestamp as a prefix. It's also very handy to group all the traces functions in this central place to be able to easily turn them off.

Important warning : As flash.net.netGroup has been added in flash Player 10.1, make sure you have updated Flash Builder to work with the Flex 4.1 version.

Now you can create a new Flex Library Project named LibNetGroup. Create a 'netgroup' package in this project, in which we will add our files.

Step 4: A custom NetGroupEvent to Dispatch Application-Level Updates

First, let's start by our custom event. Our manager will be used to let the project know when the group is ready to be used (connectedToGroup event), when we're disconnected, when a new neighbor has joined and when a message has been received. Obviously many others events might be added, but let's keep it simple for now, and you'll be able to extend it later. Rather than extend Flash's DynamicObject, I choose to simply have two fields for storing optional data when the event carries the details of a received message.

Step 5: Our own Message Format to Bypass Pitfalls

Next easy step, we'll set-up a message format. A message will have a subject, a sender and some data. The subject is useful for routing received message in a real application, the data will be some kind of generic object. Remember the netGroup.post() method we briefly mentioned in step 2. It takes an object as argument. So we'll have some serialization methods to create a VoNetMessage from an object, and create an object from a VoNetMessage instance. This serialization method will be implemented by a simple getter method, and we'll add here some salt.

Do you remember peer to peer automatically handle the routing of the messages. If a peer gets a message it has already forwarded, it will consider this message as old and will not send it again. But what will happen when our application will send small simple notification like a single string 'endOfVote' to notify clients the vote has ended. If we start a new vote, then stop it, this very simple message will be send one more time. Exactly the same contents. But we need to make sure the peers are aware this is yet a new message. The easiest way to make sure of this is to add some salt in our message, when serializing it before sending it. In this example I use a simple random number, but you might want to add a timestamp, or start creating some unique messages IDs. You're the boss. The most important thing is to be aware of this pitfall. Then you're up to handle it the way you prefer.

To send a message to the group, it will be as simple as instantiating a VoNetGroupMessage, populating its subject and data properties, then passing its object getter in the netGroup.post() method. When a message will be received from the group, instantiating it with the object received as the constructor's argument will automatically populate the properties, ready to be read, like this :

var message:VoNetGroupMessage = new VoNetGroupMessage(objectReceived)

Step 6: Wiring it all Together: the NetGroupManager Class

The manager will be the 'black box' that our application will interact with to communicate with the peers. We don't want that our business logic have to take care of how the network communication is done, we just want to send and receive messages. So let's start by defining the public API we want for our manager:

It will dispatch events about what happens, therefore it will extends EventDispatcher

It will be easy to set up to use our own rtmpServer value, developerKey if needed, applicationName and groupName. No need to edit the class to use it for another application.

A connect() method to connect to our peers.

A disconnect() method

A sendMessage() method.

Let's look at this in details

NetGroupManager will Dispatch Meaningful Events

It will extend EventDispatcher, and will dispatch the events we have prepared in NetGroupEvent. We had the event meta-tags to make their usage easier:

/**
* Event sent every time a message is received.
*/
[Event(name="posting", type="netgroup.NetGroupEvent")]
/**
* Event sent once the netConnection and netGroup connections have been
* successful, and the group is ready to be used.
*/
[Event(name="connectedToGroup", type="netgroup.NetGroupEvent")]
/**
* Event sent the netGroup connection is closed.
*/
[Event(name="disconnectedFromGroup", type="netgroup.NetGroupEvent")]
/**
* Event sent the netGroup connection is closed.
*/
[Event(name="neighborJoined", type="netgroup.NetGroupEvent")]

It will also have some public bindable properties to have a look at the group state. Be careful, I have let them as public properties to make the code smaller and easily bindable, but these shouldn't be kept externally editable in a real world project.

NetGroupManager will be Easy to Configure

Let's give it some public properties as arguments: rtmfpServer, cirrusDevKey, applicationName and groupName. This should sound familiar if you have read the previous steps.

/* ------------------------------------------------------------------ */
/* --- Connection parameters --- */
/* ------------------------------------------------------------------ */
/**
* Using a serverless rtmfp does work for local subnet peer discovery.
* For internet usage with Cirrus server, something like
* rtmfp://cirrus.adobe.com/"; will be more useful
*/
public var _rtmfpServer:String = "rtmfp:";
/**
* If you are using a Cirrus server, you should add your developer key here
*/
public var _cirrusDevKey:String = "";
/**
* applicationName is the common part of the groupSpecifier name we will be using
*/
public var applicationName:String = "active.tutsplus.examples.netGroup/";
/**
* groupName is added to applicationName to be able to manage several
* groups for the same application easily.
*/
public var groupName:String = "demoGroup";

NetGroupManager Will be our API to Send Messages to our Peers...

It will have a few method : connect(), disconnect() and sendMessage(). The connect method is almost already known, as all the important stuff in there have been seen in the previous step. The disconnect method is simply a wrapper of the netConnection.close() method.

The sendMessage method will use everything we have explained in the VoNetGroupMessage structure, therefore there's no surprise in its implementation: it instantiates a VoNetGroupMessage object, populate its fields, then serialize it using its object getter and use it for the netGroup.post() method.

... and NetGroupManager will be our Central Place to Read Message Received From Peers.

We already had a quick look of the NetStatusEvent values in the previous steps; the only missing part is the messageReceived handler, called when a NetGroup.Posting.Notify status has been received. In the NetStatusEvent listener, we instantiate the message using the object we receive, then pass it to the messageReceived method:

This messageReceived handler will dispatch the message. The business layer which might have subscribed to this event will then handle the inner details of the message, the job of the netGroupManager, which is handling peer connections, sending and receiving data, is done.

Step 7: Quick Test Creating a Basic Chat Using the NetGroupManager

Let's create a new Flash project in Flash Builder, which I named Demo_NetGroupChat. As this project will use the manager we just wrote, we have to add a reference to the manager project. This is simply done by opening the project properties and in the Flex Build Path clicking the "Add a project..." button and selecting the LibNetGroup project.

We will bind an instance of our NetGroupManager as lanGroupMgr variable. We will add a connect button which will call the connect method of our manager, some labels binded to the public properties of our managers connectionState, joinedGroup and neighborCount.

Here comes the source and demo of this test application. To have something to test, you should open this URL several times, ideally on several computers on the same network. If you don't have several computers right now, you can open multiple tabs in multiple browsers (although the real interest of the feature won't be as obvious as on on a network, it's handy for quick testing).

Step 8: Building a Sample Peer Application: PeerVote

We will now build an application a little more complex than a chat, to see how to handle different kind of messages, and how to modify the application state depending on the events received from the other peers on the network. In the same NetGroup, we will allow users to have the choice to be:

voteManager. There will be only one manager in a group at once. The voteManager will edit a question and the available answers, then send a vote request to all peers with theses data. The voteManagers will receive and display the vote results in real time, each time a client answers. Then the voteManager will dispatch the vote results to the other peers of the group as soon as all the clients connected have answered, or if the manager manually decided to stop the vote.

voteClient. A client will display a 'please wait' screen until it receive a question, then will show the possible answers to the end-user. When the user selects his answers, the client will send the selected answers to the vote manager. At last when the vote manager dispatch the vote results, the client will display how many time each answer has been selected by users of the group.

To make things a little smarter, we will build only one application. When a user 'A' joins the group, he might choose if he want to be a manager or a client. If a vote manager is already set by the user 'B' in the group, it warns 'A' about his existence, therefore the application 'A' will automatically choose to be a vote client and will start to wait for a question.

Step 9: Listing the Basic Bricks of our Application

In the next steps we will list every item we need to build this vote application: obviously the NetGroupManager we build in the previous steps, but also a model, an event, some views to display an UI. And finally a controller to link the model to the views.

Create a new project in Flex builder and give it the name "Demo_NetGroup_Votes". Add a reference to the LibNetGroup project, like we already did for the quick chat test. This way we will be able to use our NetGroupManager classes.

In the src folder, let's create some folders that we will fill in the forthcoming steps:

'models', this package will store our model

'events', this package will contain our custom event

'controllers', in which we will put the controller of our vote application

'screens' which will be fill with the MXML components we will use as views for our application

Step 10: A Simple Model: VoteDefinition

The vote application will send a "vote definition", containing information about the vote, to the clients. These clients will have to read this question and display it. In order to make things clean, we will create a very simple model so that everything as a name and a type. Although our question might be simplified to a string list for this test, using a model gives an idea of how to build it in a way that makes it easily expandable, easy to read and debug.

Start by create a new VoteDefinition.as class in the models package. We will add some public properties to store the question and up to five answers, as Strings. The constructor of this class will read a generic Object and fill its properties with theses values.

Step 11: A Simple Event, VoteEvent, Specifying the States Transitions

Our application will have to react to several notifications. As you extend the application you will be able to add some functionalities, and events, but for our basic test, the list of custom events we will need is really short:

roleSet event, when the application know if it should behave as a vote manager or as a vote client

startVote, when a vote has to be displayed to the end user by a vote client

stopVote, when the manager ask everyone to stop the vote interactions

showResults, when all the votes have been merged and dispatched to the clients, for them to display the results

answersReceived, when the vote manager receive the answer from a client, in order to update the current results in real time on the manager screen.

We also need to store some information, like the vote details, or the results to display. Rather than using a dynamic object, we just add a data property, typed as Object, to store the VoteDefinition transmitted, an array of answers index, or any other relevant data... (more on this later)

To turn this into code, create a VoteEvent class extending flash.events.Event in the events package. We add the several event types we have just listed, and the data property:

Step 12: Listing our Views

We have now the data we will play with, the event to notify the changes. But we still haven't anything to display. Let's list the views we will need:

a main container, which will be the common part of the application. It will initialize the VoteController and ask for a connection to the group, and then might switch to a few states: init, roleChoice, voteManager and voteClient.

If you look at the role setter function, you will notice that the roleSet event is dispatched, and that we call a _handshakeVoteManager() method. This method will be quite simple: if we're a voteManager, then we tell our peers that we're here. We will look in a few moments how our peers will listen for this message.

We have used netGroupMgr.sendMessage(), a function of our NetGroupManager. This means we have to initialize this manager and start a connection before. This is what we do in the constructor of this VoteController. This is also the right place to add the listeners for netGroupManager events to know when a message is posted, when a peer joined, and more basically if we are connected in a group.

In this constructor we have created an instance of our NetGroupManager, set the name of our group and started a connection. We also added some listeners for the basic group events that our manager will send us. Let's see how to handle theses group events.

When the connection is done (remember that we're dealing with the connection success of the NetGroupManager, which means it has successfully connected to a NetConnection then to a NetGroup), we will set a "connected" flag to true and dispatch a VoteEvent to let the application know that the initialization is done.

When a peer joins the group, so the NetGroupManager give us a neighborJoined event, we tell this peer if we're the voteManager. This way as soon as the application connects to the group, it will be informed by the voteManager if such a manager already exists in the group.

The last netGroupManager listener we have defined is the posting callback. It will be triggered every time we receive a message from the group. To handle these messages, we then just have to perform a switch/case with the subject values we have already set before:

Do you remember the _handshakeVoteManager() method from Step 13 that calls netGroupMgr.sendMessage(SUBJECT_SET_VOTE_MANAGER_EXISTENCE, {});?

In the SUBJECT_SET_VOTE_MANAGER_EXISTENCE branch we see the other part of the message, when a peer receives it. When receiving this information, if the role of this peer hasn't already been set, then we force it to be a client, as a manager already exists in the group. As the handshakeVoteManager method is called every time we set our role and every time a peer joins the group, we are sure that every new peer will aware of the existence of any manager.

The stopVote message dispatches an event, more on this in the view steps.

For the startVote, submitAnswer and showResults branchs, we use the data contained in the message. The type of this data will vary depending on the subject.

The startVote branch will take this message and dispatch it in a startVote event to the views:

/**
* doStartVote will be called by submitVote for the VoteManager, and from
* onNetGroupMessage for the voteClients, when receiving a SUBJECT_START_VOTE message
*/
protected function doStartVote(def:VoteDefinition):void
{
var evt:VoteEvent = new VoteEvent(VoteEvent.START_VOTE);
evt.data = def;
dispatchEvent(evt);
}

The showResults works almost the same way, but the data is kept in the VoteController as public buffers, and the event is dispatched.

We will see that each client send its answers as an Array of integers. If the clients have selected the answer two and four, the message will be [2,4]. This is why the subject answers handle the message variable as an Array when calling registerClientAnswers.

protected function registerClientAnswers(answers:Array):void
{
// answers contains the position and the answers selected by the user, increment our counters
for each (var ind:int in answers)
{
currentVoteAnswers[ind]++;
}
currentVoteUsers++;
// notify the manager of the vote progression
dispatchEvent(new VoteEvent(VoteEvent.ANSWERS_RECEIVED));
if (currentVoteUsers >= totalVoteClients)
{
// We have received all the answers
dispatchEvent(new VoteEvent(VoteEvent.STOP_VOTE));
netGroupMgr.sendMessage(SUBJECT_STOP_VOTE, 'all answers received');
}
}

The manager's controller store how many time every answers has been selected simply by incrementing the different values of an array of answers number. It then dispatch a event to let the views know that a new answer has been received. This way the manager will display the results in real time. At last if all the votes we are waiting have been received, we stop the vote, and notify both the local user with an event and the peers with a stopVote message. As we have seen a few lines before, this message will dispatch the same stopVote event.

Our manager initializes correctly and handles the messages it receive from the group. Now we need to make sure these messages are sent, and therefore we still need to add a few public methods for our views to be able to start a vote and submit their answers.

To start a vote, the view will only have to pass the VoteDefinition (i.e. the texts of the question and answers). We will initialize our buffers to store the questions, remember how many answers have been received and how many we will wait for. Then we send the startVote message to our peers. By storing the totalVoteClients number when starting a vote, I'm sure I'll wait for the correct answer count, even if new peers join the group during the vote (these peers will have to wait for the next vote to participate).

The submit answer is trivial: we already said our answers are a simple array of the position of the answers selected by the end-user. We just send that to the manager:

init state will be the default one, when we wait the controller to dispatch the connectedToGroup event to let us know we are ready to work. Then the application will enter the roleChoice state, waiting that the role of this instance is set to manager or client. At last when the role is set it will enter either the voteManager or the voteClient state, depending on the role.

Once the application is connected to the group, we've seen that the initDone handler modify the state of the component to 'roleChoice'. Let's set the user interface of roleChoice: two buttons to choose one role or another.

The important thing here, is that we directly modify the role property of the controller. We have seen that the controller will dispatch a roleSet event, and in the _initializeHandler of this component we have registered for this event. This means that the onRoleSet method will be called when the user click on one of this two buttons, or when the application's role is forced to client by the controller because another voteManager has notify its existence to the group.

All the initialization is done by now, and we have to create the views for the manager and client roles. From our component it will be as simple as including one component or another depending on the state:

Step 15: Preparing the Gauge to Display Results

Both the manager and the client will need to display the number of times an answer has been selected, so first create a little component for this.

We will extend a progress bar to use it as a gauge. In the screens package, create a new MXML component, extending mx.controls.ProgressBar. We will set its default value for manual mode and place the label in the center, then override the setProgress method to build the label's text:

In the screens package, create a Manager_Vote component extending spark.components.Group.

The vote manager will have to listen to some event of the VoteController, so let's add a creationComplete listener and fill it with the event listener's definitions to listen to startVote, stopVote and answersReceived events. We have already seen in the VoteController implementation when these events are dispatched.

Now the main part of our UI. Basically, editing a vote is modifying the few text inputs for the questions and the possible answers. For each answer we had a gauge, which visibility will be set to true only for the _voting state (voteInProgress and voteStopped states).

In the second half of this code block, you have seen we put three buttons. The submit vote button to start a vote, which is only enabled for the editVote state. Then the stop vote and new vote buttons which are only included in their relevant states.

To start a vote, we have to create a VoteDefinition object, fill it with the texts and call the submitVote method of the controller we have seen before.

Now we're in the voteProgress state. This mean we might receive answersReceived and stopVote events from the controller, and the user may press the stop vote button. The stop vote part is really easy: it ask the controller to stop and send results to everyone:

For the answerReceived, we have to read the currentVoteAnswers buffers from the controller (which is updated every time answers are received, and before broadcasting this event). This way we just have to call the setProgress method of every gauge and here we are, we have our real-time vote feedback:

The new vote button simply set the current state to editVote. This way the gauges aren't visible any longer, the fields can be edited, and the submit vote button is enabled. Everything is ready to edit a vote, send it, and display the results.

Step 17: Client_Vote, a View to Display Choices to the End-User

The screens.Client_Vote component is built on the same model as the manager; as it extends Group it will have a few states:

waitingVote when no vote has been received yet,

voteInProgress when a vote has been received and the user can enter his answers

waitingVoteEnd, when the user submitted his answers but not every other clients has done so, so that the vote is still running on other computers (or browsers tabs)

voteResults, when everyone has answered or when the manager manually stopped the vote, and sent the results to all the peers

When a startVote event is received, the VoteDefinition object will be stored in a bindable property, and we will enter the voteInProgress state. If this is not the first vote we received, the vote UI is already created, and its checkboxes might be selected, so we make sure everything is unchecked in this case.

Now the UI of the voting form. Like the manager we have components for the question and each answer. For the client each answer is a checkbox, which is only enabled while the vote is in progress (i.e. we are in the voteInProgress state). The labels of the question and answers are binded to the voteDefinition's properties. At last we have a submit vote button to send our choices.

When the user presses the "submit vote" button, we will create an array with the positions of the selected answers and call the submitAnswer method of our VoteController. Remember that the controller then sends these answers to our peers in the group. The network operations are only done by the controller. The views and models don't care about how this is done, they just call the submitVote public API we have written in our controller. As a user can answer vote only one time, we immediately update our state to make sure that the vote is stopped.

At last, when the vote manager in the group sends the vote results, we get a showResults event from the vote controller which triggers our onShowResults handler defined in the creationComplete handler.

Step 18: VoteResults: Displaying the Last Vote with Labels and Gauges

The screens.Vote_Results is a simple rewrite of the manager interface for the gauges and using text labels rather than TextInput components. When the controller receives a showResults event, we've seen it stores all the details in its currentVoteDefinition and currentVoteAnswers properties. The result component read these properties to set the value of the gauges and text labels.

That's all, we have built everything and we are ready to test in on several computers.

Step 19: Deploy and Test

Now that every part have been put together, you're done, there's no configuration to do to make it work for interconnecting several computers : open you this demo movie multiple times on some computers and see how they automatically discover their peers, receive the vote from the manager and how everyone get the results.

Step 20: Where to go From Here?

So what does RTMFP and the new netGroup support in Flash Player 10.1 means for us, Flash developers? Obviously creating a audio/video chat becomes really easy. But having several computers automatically connect between each other without having the end-user to do any network configuration or enter any IP address or server URL is really great for many other things. Think about quiz for kiosk or stand animations where each participant has his screen and UI, and the animator has a manager interface.

Another case might be a remote log, like a monitoring tool, automatically receiving the updates from all the kiosks, or all the student computers.

Having an easy way to implement zero-configuration networking between flash clients open a very wide range of features we will be able to bring into our applications, might it be for learning, monitoring, interacting...

If you want to go further, there are plenty of excellent resources on the Internet.

The first entry point should be the Actionscript documentation, where netGroup.post() method is very well covered and gives everything you need to get the first steps. Of course all the other classes of the flash.net package we have used in this article are worth reading, like NetConnection, GroupSpecifier and NetStream.

Adobe's 2009 Max session about RTMFP gave me a lot of information, like the need for a message to be unique to be considered new when using NetGroup.post(). Moreover, there's really a lot of information there about media multicasting that is worth knowing.