April 17, 2012 | Chris Rock

PHP AJAX Long-polling Comet Demonstration

One thing missing from web applications that is found in, say native applications, is the ability to “push” data or events to the application. HTTP was built on the idea that once you got the document, you were done. Clever developers have found a couple of ways to get around this:

  • Web Sockets – Advanced way for browsers to communicate with a web sockets server to send and receive data (supported only by newer browsers)
  • Hidden iframe – use an iframe that references a file that never closes to which javascript snippets are written
  • Short polling – ping the server every few seconds to see if there have been any changes
  • Long polling – open a connection to the server and wait until there has been a change before ending the file, then starting again

Externally controlling a website

I had wanted to create a page in which a user could use a mobile device to control a sprite on another page. I believe that future Web Apps will use functionality like this, especially as mobile devices become more ubiquitous. After some research (and false starts with Web Sockets) I decided to use the long polling method to accomplish this. To do that, we’ll need to make the following:

  • External Data – Storage accessible by both the controller and receiver
  • Controller – A page that will send events to the external data storage
  • Receiver – A page that will listen for changes to the external data storage

Before we get into the details, let’s actually see it in action. You’ll need to open the following links in two different browsers, windows, or devices. It’s especially impressive to open the controller in a mobile device and the receiver on a desktop browser.

The receiver will have an image on it, and the controller with have a D-pad-like graphic. Click the d-pad up, down, left, or right and the graphic on the receiver should move. Very impressive, no? Let’s look into how this works. If you’d like, you can download the source code, which goes into more detail.

The External Data

You can use anything for this. For simplicity we’ll just be using a flat text file called “move.txt”

The Controller

Let’s start with the easy part–sending directions to the external storage. For simplicity, I’ve only included the relevant code.

<ul class="control-pad">
    <li>
        <a class="up" data-direction="up" href="#"></a>
    </li>
    <li>
        <a class="down" data-direction="down" href="#"></a>
    </li>
    <li>
        <a class="left" data-direction="left" href="#"></a>
    </li>
    <li>
        <a class="right" data-direction="right" href="#"></a>
    </li>
</ul>

Here’s the javascript for controller.php. It uses jQuery, so be sure to include it.

$(document).ready(function() {
    $('.control-pad a').on('click', function(e) {
        e.preventDefault();
        var dir = $(this).attr('data-direction');
        // Send direction
        $.ajax({
            url: 'controller-process.php',
            data: { 'direction' : dir }
        });
     });
});

Next, we need to create the controller-process.php file for the controller to call with AJAX.

// External data source
$filename = dirname(__FILE__).'/move.txt';
// Write direction to file
$fh = fopen($filename, 'w') or die('Cant open');
fwrite($fh, $_GET['direction']);
fclose($fh);

This is pretty simple–it writes the direction sent by the controller into the external data file. This will change the timestamp on the file, which will signal the monitor that we have a new command to process.

The Receiver

This is the central file for the project. It displays a sprite and provides javascript so that sprite can be controlled. It “listens” by doing an AJAX call to a script named move-monitor.php.

The only HTML needed in this file is just a div for the sprite itself.

<div class="sprite"></div>

Then there’s the javascript for moving the sprite around

function moveSprite(direction) {
    var step = 50;
    if(direction == 'left') {
        $('.sprite').css('left', '-=' + step);
    } else if(direction == 'up') {
        $('.sprite').css('top', '-=' + step);
    } else if(direction == 'right') {
        $('.sprite').css('left', '+=' + step);
    } else if(direction == 'down') {
        $('.sprite').css('top', '+=' + step);
    }
}

Now for the important part–the AJAX call to the move-montior.php script.

var timestamp = null; 
function listener() {
    // Fetch info from monitor. This file will not completely download until it changes
    $.ajax({
        type: 'GET',
        url: 'move-monitor.php',
        data: { 'timestamp' : timestamp },
        dataType: 'JSON',
        async: true,
        cache: false,
        success: function(data) {
            moveSprite(data['direction']);
            timestamp = data['timestamp'];
            listener(); // Run listener again
        }
    });
}

Notice that this function get’s called again once the AJAX call is complete. We’ll come back to that later. Also notice that async is set to true and cache is set to false–that’s important.

And then the move-monitor.php script itself.

// External data file: can use any source, DB etc.
$filename = dirname(__FILE__).'/move.txt';
 
$lasttime = isset($_GET['timestamp']) ? $_GET['timestamp'] : '';
$currenttime = filemtime($filename);
 
// Loop until the files timestamp changes
while($currenttime <= $lasttime) {
    usleep(1000); // slow it down a bit
    clearstatcache();
    $currenttime = filemtime($filename);
}
 
// Send json to receiver
echo json_encode(array(
    'direction' => file_get_contents($filename),
    'timestamp' => $currenttime
));

This is the real meat of what we’re trying to do, so let me explain it more specifically. This file will loop until the timestamp on the data file is different than the timestamp sent by the controller. Because the file won’t be “finished” until this happens, the connection remains open until we get a new command to process. Once there’s a new command, the data is sent to the receiver and the process begins again.

That’s basically it. This is a surprisingly fun little script to play with and it seems to impress people, especially when you’re controlling the website with your mobile device.

From here the next step might be to implement a way to have multiple people access the same page without overwriting each others moves–maybe each person could control their own sprite? Be creative and have fun with it.

  • Development
  • AJAX
  • development
  • javascript
  • PHP