Article

Distributed Processing With Flex and AIR

Page: 1 2 3 4 Next

Building the Processing Library

Now that we have the server built, it’s time to start building the client. There are two ways to build AIR applications. The first is using DHTML, so that your application is written in a combination of HTML and JavaScript. The second way is to build a Flash or Flex application. We’ll go with the Flex option, and use the Flex Builder 3 IDE from Adobe to build it. Flex Builder 3 has AIR support built right in, along with a very useful debugger, and it’s free as a trial. Of course, if you’d rather use your own editor and the Flex SDK and AIR SDK, then those are available as free downloads too.

Once we have Flex Builder 3 installed, the first step is to build a library for connecting to the server. Since we’re building two clients, both of which want to connect to the server, it’s best if both use the same code base. To create the library, we choose File > New > Flex Library Project. This will bring up the New Flex Library Project wizard; we name the project salesmanLib, then click Finish. This will create the salesmanLib project, which should be visible in the Flex Navigator panel.

From there, we select File > New > ActionScript Class from the menu to create a new class called Server in the com.distributed namespace. This singleton object will act as a proxy for the web service. It will do all of the connecting to the web server, as well as maintain the list of cities and the current starting city.

A “singleton” means that there can only be one of these objects around at a time—you access that one object by calling the instance method on the class.

The code for the Server class is shown in Listing 3.

Listing 3. Server.as  
package com.distributed  
{  
 import flash.events.EventDispatcher;  
   
 import mx.messaging.ChannelSet;  
 import mx.messaging.channels.AMFChannel;  
 import mx.rpc.events.ResultEvent;  
 import mx.rpc.remoting.Operation;  
 import mx.rpc.remoting.RemoteObject;  
 
 public class Server extends EventDispatcher  
 {  
   private static const SERVER_URL:String = "http://localhost/amfphp/gateway.php";  
     
   private static var _instance:Server = new Server();  
     
   public static function get instance() : Server { return _instance; }  
     
   private var _ro:RemoteObject;  
   private var _getCities:Operation;  
   private var _requestCity:Operation;  
   private var _updateProcessing:Operation;  
   private var _setSequence:Operation;  
     
   private var _cities:Array = [];  
   private var _cityById:Object = {};  
   private var _startCity:int = 0;  
     
   public function get cities() : Array {  
     return _cities;  
   }  
     
   public function get startCity() : int {  
     return _startCity;  
   }  
     
   public function cityById( id:int ) : Object {  
     return _cityById[ id ];  
   }  
     
   public function distance( fromID:int, toID:int ) : Number {  
     var f:Object = cityById( fromID );  
     var t:Object = cityById( toID );  
     var dx:Number = f.lat - t.lat;  
     var dy:Number = f.lon - t.lon;  
     return Math.sqrt( ( dx * dx ) + ( dy * dy ) );  
   }  
         
   public function Server()  
   {  
     var cs:ChannelSet = new ChannelSet();  
     var amfc:AMFChannel = new AMFChannel("CityService",SERVER_URL);  
     cs.addChannel( amfc );  
     
     _getCities = new Operation( _ro, "getCities" );    
     _getCities.addEventListener( ResultEvent.RESULT, onGetCities );  
     _requestCity = new Operation( _ro, "requestCity" );    
     _requestCity.addEventListener( ResultEvent.RESULT, onRequestCity );  
     _updateProcessing = new Operation( _ro, "updateProcessing" );    
     _updateProcessing.addEventListener( ResultEvent.RESULT, onUpdateProcessing );  
     _setSequence = new Operation( _ro, "setSequence" );    
     _setSequence.addEventListener( ResultEvent.RESULT, onSetSequence );  
       
     _ro = new RemoteObject( "salesman.CityService" );  
     _ro.channelSet = cs;  
     _ro.source = "salesman.CityService";  
     _ro.operations = [ _getCities, _requestCity, _updateProcessing, _setSequence ];  
   }  
     
   public function updateProgress( progress:int, seq:Array ) : void {  
     _updateProcessing.send( _startCity, progress );  
     _setSequence.send( _startCity, seq );  
   }  
     
   public function getCities() : void {  
     _getCities.send();  
   }  
 
   public function requestCity() : void {  
     _requestCity.send();  
   }  
 
   private function onGetCities( event:ResultEvent ) : void {  
     _cities = event.result as Array;  
     for each ( var city:Object in _cities ) {  
       _cityById[ int( city.id ) ] = city;  
     }  
     dispatchEvent(new ServerEvent(ServerEvent.GET_CITIES));  
   }  
   private function onRequestCity( event:ResultEvent ) : void {  
     _startCity = int( event.result.id );  
     dispatchEvent(new ServerEvent(ServerEvent.REQUEST_CITY));  
   }  
   private function onUpdateProcessing( event:ResultEvent ) : void {  
     dispatchEvent(new ServerEvent(ServerEvent.UPDATE_PROCESSING));  
   }  
   private function onSetSequence( event:ResultEvent ) : void {  
     dispatchEvent(new ServerEvent(ServerEvent.SET_SEQUENCE));  
   }  
 }  
}

There is a lot of code here, but it’s all fairly simple. In the constructor, we create a connection to the server as well as to objects that represent each of the four different server methods (or operations). These operations all have listeners that are called when the method completes on the server. AMF, as well as most of Flex, is asynchronous. So when you make a call the control immediately returns. When the call completes, you receive an event back from the operation method saying that it’s completed.

Let’s take getCities as an example. The getCities method calls the send method on the _getCities operation; this invokes the web server. But control comes back to the application immediately. Once the server has returned all of the data, the onGetCities method is called. That method then stores the list of cities in the _cities array, as well as creating a quick-lookup object called _cityById, which has the list of cities indexed by ID.

The onGetCities method then dispatches its own custom event called a ServerEvent, which says that the cities have been downloaded by specified the event type of GET_CITIES.

Create the ServerEvent class as before, by selecting File > New > ActionScript Class. The code for this class is shown in Listing 4.

Listing 4. ServerEvent.as  
package com.distributed  
{  
 import flash.events.Event;  
 
 public class ServerEvent extends Event  
 {  
   public static const UPDATE_PROCESSING:String = 'UPDATE_PROCESSING';  
   public static const GET_CITIES:String = 'GET_CITIES';  
   public static const REQUEST_CITY:String = 'REQUEST_CITY';  
   public static const SET_SEQUENCE:String = 'SET_SEQUENCE';  
     
   public function ServerEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false)  
   {  
     super(type, bubbles, cancelable);  
   }  
 }  
}

The final object we need for the library is a data object that represents a Sequence, or a route, that the salesmen will take as he goes from city to city. The Sequence class, created in the same way as the previous two classes, is shown in Listing 5:

Listing 5. Sequence.as  
package com.distributed  
{  
 public class Sequence  
 {  
   private var _sequence:Array = [];  
   private var _distance:Number = 0;  
     
   public function get sequence() : Array {  
     return _sequence;  
   }  
     
   public function get distance() : Number {  
     return _distance;  
   }  
     
   public function Sequence( cities:Array, startCity:int )  
   {  
     _sequence.push( startCity );  
     var seq:Array = [];  
     for each ( var city:Object in cities )  
       if ( int( city.id ) != startCity )  
         seq.push( { rand:Math.random(), city:int( city.id ) } );  
     seq = seq.sortOn( 'rand' );  
     var last:int = startCity;  
     for each ( var cObj:Object in seq ) {  
       _distance += Server.instance.distance( last, cObj.city );  
       _sequence.push( cObj.city );  
       last = cObj.city;  
     }  
   }  
 }  
}

The task of the Sequence class is to create the sequence and to calculate the distance of the route. To save on implementation space, we’ll just have the Sequence create a random route. (Obviously this isn’t optimal, but the point of this article is to demonstrate how to set up processing clients and servers, not really to attempt to solve the travelling salesman problem!)

The library now contains all of the tools that we need to build both the processor application and the monitoring application. We’ll start by building the processor.

If you liked this article, share the love:
Print-Friendly Version Suggest an Article

Sponsored Links