Article

Home » Client-side Coding » Flex Tutorials » Distributed Processing With Flex and AIR
SitePoint Feature Article

About the Author

Jack Herrington

Jack Herrington Jack Herrington is an engineer, author, and presenter who lives and works in San Francisco, USA. He writes regularly on the topics of Flex, Ajax, Silverlight, PHP, and Ruby on Rails at http://jackherrington.com.

View all articles by Jack Herrington...

Distributed Processing With Flex and AIR

By Jack Herrington

October 15th, 2008

Reader Rating: Not yet rated

Page: 1 2 3 4 Next

The starship Enterprise can scan the entire surface of a planet in one second and report back that there are signs of life. But for those of us who don’t live in the world of sci-fi, searching for extra-terrestrial intelligence is a much more involved process; the computational power required is immense. So immense, in fact, that the scientists at the Search for Extraterrestrial Intelligence (SETI) developed a distributed application that ran on Mac and Windows called SETI@Home. This application allows us non-scientists to contribute the processing power of our machines to help SETI find intelligence in the stars.

SETI@Home is a wonderful example of the power of distributed computing. You give a little chunk of data to a large number of computers. Let them chew on it for a while, and when they’re done they spit back the results. The larger the number of computers, the faster the processing.

So how can you apply the power of distributed computing to your own cool project? I suggest using Adobe’s Integrated Runtime (AIR) technology. With AIR, you can build a single application in ActionScript, or in HTML/JavaScript, that runs on Windows, Macintosh, and Linux. And that application can use the network to request data from the server, process it locally, and send the results back to the server once the processing is completed.

In this article, I’m going to demonstrate how to build all the required components of a distributed processing system. The first is the web server component that coordinates the delivery of data to clients, and receives the results from the clients. The second is the processing client, which requests the data, processes it, and returns the results to the server. The final piece is a monitoring client that monitors the activity on the server so that you can see the processing in motion.

Readers who have followed this series will guess that there’ll be a quiz at the end, to test you on what you’ve learned, and help it all sink in that much further. The first 100 people to give the quiz their best shot will receive a free copy of the Adobe AIR for JavaScript Developers pocket guide in the post, courtesy of the kind people at Adobe Systems. Remember, too, that the book can be downloaded free as a PDF!

Building the Server

The little sample distributed application we’re going to build is a dummy solution for the “traveling salesman problem.” The problem appears to be fairly simple—you have a salesman who has a list of cities to visit. You have to deduce the most efficient route from a given starting point. This problem, however, has been proven to be one of the most difficult problems known to computer science. In this article, we’re just going to provide a random route generating stub. If you’d like to replace the stub with an effective algorithm, feel free—you’ll be on your way to winning yourself a Fields Medal!

It’s the job of the server to provide the list of cities, along with their latitude and longitude, as well as providing the starting city that the client should process. Each client works on a different starting point. Once it’s finished, it sends the fastest route back to the server and requests a new starting city.

We’ll write the server in PHP 5 and use the AMFPHP project to provide an AMF API to the client. If you’re unfamiliar with the Action Message Format (AMF), it’s a way of making remote procedure requests from a client to a server. It works very well with Flex, which is what we’ll be using for the client.

Setting up AMFPHP couldn’t be much simpler. Download the latest code from the site and copy the code into the Apache home directory. At this point, you also may want to download the code archive for this tutorial, so you can play along at home. In this example, I’ve put the code in a subdirectory called amfphp. If you browse to http://localhost/amfphp/browser/, you’ll be presented with an empty service browser. In a second, we’ll see how easy it is to start populating this list with actual services.

We’ll also be using SQLite to store the data on the server. The schema for the database is shown in Listing 1.

Listing 1. salesman.sql

CREATE TABLE city (
 id INTEGER PRIMARY KEY AUTOINCREMENT,
 name TEXT NOT NULL,
 lat REAL NOT NULL,
 lon REAL NOT NULL,
 processing REAL
);

INSERT INTO city(name, lat, lon, processing) VALUES('Albany, N.Y.', 42, 73, null);
INSERT INTO city(name, lat, lon, processing) VALUES('Albuquerque, N.M.', 35, 106, null);
INSERT INTO city(name, lat, lon, processing) VALUES('Amarillo, Tex.', 35, 101, null);

CREATE TABLE sequence (
 start INTEGER,
 element INTEGER,
 city INTEGER
);

We load this into the database by executing these commands:

$ sqlite3 -init salesman.sql salesman.db
Loading resources from salesman.sql
SQLite version 3.4.0
Enter ".help" for instructions
sqlite> .exit
$

The schema is pretty simple. The city table holds the names and locations of the individual cities, along with a processing field. This processing field holds the number of solutions run against the city. If it’s non-null, it means it’s been processed. So when a new client comes along, the server will give them a starting city where the processing field is null.

The sequence table holds the final sequence that the client develops as a solution when given the starting city.

Next, we need to build a PHP class for accessing and manipulating this information. Create a directory called salesman inside AMFPHP’s services directory, and place the following file inside it:

Listing 2. CityService.php
<?php

class CityService
{
 private $_user;
 private $_password;
 private $_dsn;
 
 public function __construct()
 {
   $this->_user = '';
   $this->_password = '';
   $this->_dsn = 'sqlite:/Users/craiga/salesman.db';
 }
 
 public function getCities()
 {
   $connection = new PDO($this->_dsn, $this->_user, $this->_password);
   $statement = $connection->prepare('SELECT * FROM city');
   if($statement->execute()) {
     $result = $statement->fetchAll(PDO::FETCH_OBJ);
   }
   else {
     $errorInfo = $statement->errorInfo();
     throw new Exception(sprintf('PDO Error %d: %s', $errorInfo[1], $errorInfo[2]));
   }
   return $result;
 }

 public function requestCity()
 {
   $connection = new PDO($this->_dsn, $this->_user, $this->_password);
   $statement = $connection->prepare('SELECT * FROM city WHERE processing IS NULL');
   if($statement->execute()) {
     $result = $statement->fetch(PDO::FETCH_OBJ);
   }
   else {
     $errorInfo = $statement->errorInfo();
     throw new Exception(sprintf('PDO Error %d: %s', $errorInfo[1], $errorInfo[2]));
   }
   return $result;
 }

 public function updateProcessing($city, $amount)
 {
   $connection = new PDO($this->_dsn, $this->_user, $this->_password);
   $statement = $connection->prepare('UPDATE city SET processing=:amount WHERE id=:city');
   if(!$statement->execute(array('city' => $city, 'amount' => $amount))) {
     $errorInfo = $statement->errorInfo();
     throw new Exception(sprintf('PDO Error %d: %s', $errorInfo[1], $errorInfo[2]));
   }
   return $result;
 }

 public function setSequence($city, $sequence)
 {
   $connection = new PDO($this->_dsn, $this->_user, $this->_password);
   
   $statement = $connection->prepare('DELETE FROM sequence WHERE start=:city');
   if(!$statement->execute(array('city' => $city))) {
     $errorInfo = $statement->errorInfo();
     throw new Exception(sprintf('PDO Error %d: %s', $errorInfo[1], $errorInfo[2]));
   }
   
   $elem = 1;
   foreach($sequence as $seqcity) {
     $statement = $connection->prepare('INSERT INTO sequence (start, element, city) VALUES (:start, :element, :city)');
     if(!$statement->execute(array('start' => $city, 'element' => $elem, 'city' => $seqcity))) {
       $errorInfo = $statement->errorInfo();
       throw new Exception(sprintf('PDO Error %d: %s', $errorInfo[1], $errorInfo[2]));
     }
     $elem++;
   }
 }
}

?>

This simple server has four methods. The first, getCities, returns the list of cities straight out of the database. The method starts by connecting to the database. It then runs the query and stores each row in an array. It then returns the rows as an array.

To attach to the database, we use PHP Data Objects (PDO): a set of classes designed to make database access simple and portable, regardless of what database you use. In this example we’re using SQLite, but getting this code to use a MySQL, Oracle, or SQL Server backend would be a simple matter of changing the data source name (DSN).

The next three methods deal with the processing. The requestCity method returns the first city that has a null processing value, meaning that the city has not yet been processed. The updateProcessing method sets the processing value in the database for the corresponding city. And the setSequence method adds the fastest route (or sequence) to the database for the corresponding starting city.

If you reload the AMFPHP service browser, you’ll see that it will automatically recognize your new CityService, and display its four methods, as shown in Figure 1. As you can see from the code, writing an AMF service using AMFPHP is super easy. You don’t have to do any of the work you might expect in a web service, like formatting the data as XML, JSON, or CSV. All you have to do is write the methods as you would with any other PHP object. AMFPHP does all the work of turning PHP data types into their corresponding AMF types. Here you can see the browser in Figure 1.

The AMFPHP browser

Along the left side of the window are the available services. In this case we have two: the built-in amfphp service and the new salesman service. When we select the salesman service, we see the CityService within it; when we select this, we see the individual methods. We can then click on the Call button to invoke the method.

From here, we can view the results in different ways. Figure 1 shows the results as pseudo-objects. Then we click in the RecordSet view to produce the result in Figure 2.

The table of cities as a RecordSet

Pretty sweet, huh? A real web service in just a few lines of code.

The server you implement for your distributed processing system will probably have a lot more methods. But given how easy it is to implement methods using AMFPHP, you should really consider using AMF and AMFPHP as your web server technology.

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

Sponsored Links