The introduction of web sockets makes it possible for web applications to handle near real-time data without resorting to "hacks" such as long-polling.

One example of an application requiring up-to-the-minute data is sports scores. Even now, many websites which display this information use Flash applications, since Actionscript provides the facility to communicate over socket-based connections. However, web sockets allow us to replicate this functionality using only HTML and Javascript. That's what we're going to build in this tutorial, along with a lightweight "server" in PHP.

Installation and Setup

We'll base the example around the Ratchet library, which provides a PHP implementation of web sockets.

Create the following composer.json file, which both installs this dependency and sets up an autoloader for the code we're going to write:

{ "require": { "cboden/Ratchet": "0.2.*" }, "autoload": { "psr-0": { "LiveScores": "src" } } }

Now set up the directory structure:

[root] bin src LiveScores public assets css vendor js vendor vendor

You'll probably want to clone the repository, which contains a number of CSS / JS / image assets, as well as all the code from this tutorial. If you'd like to build it from scratch alongside this article, all you need to do is copy the public/assets/*/vendor folders from the cloned/downloaded package into your own at the appropriate locations.

Naturally, don't forget to run php composer.phar update , preceded by curl -sS https://getcomposer.org/installer | php if you don't have composer installed.

We'll start by building a class which resides on the server and acts as a sort of message broker – accepting connections and sending messages. Later, we'll also use it to maintain information about the games in progress. This is a skeleton implementation, to show how a generic message broker might operate:

// src/LiveScores/Scores.php <?php namespace LiveScores; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; class Scores implements MessageComponentInterface { private $clients; public function __construct() { $this->clients = new \SplObjectStorage; } public function onOpen(ConnectionInterface $conn) { $this->clients->attach($conn); } public function onMessage(ConnectionInterface $from, $msg) { foreach ($this->clients as $client) { if ($from !== $client) { // The sender is not the receiver, send to each client connected $client->send($msg); } } } public function onClose(ConnectionInterface $conn) { $this->clients->detach($conn); } public function onError(ConnectionInterface $conn, \Exception $e) { $conn->close(); } }

Important points to note;

The class needs to implement MessageComponentInterface in order to act as a "message broker"

in order to act as a "message broker" We're maintaining a list of all clients that have connected to the server as a collection

When a client connects, the onOpen event gets fired, where we add the client to our collection

event gets fired, where we add the client to our collection When a client disconnects ( onClose ), we do the opposite

), we do the opposite The interface also requires us to implement a simple error handler ( onError )

Next up, we need to create a server daemon to instantiate our new class and start listening to connections. Create the following file:

// bin/server.php <?php use Ratchet\Server\IoServer; use Ratchet\WebSocket\WsServer; use LiveScores\Scores; require dirname(__DIR__) . '/vendor/autoload.php'; $server = IoServer::factory( new WsServer( new Scores() ) , 8080 ); $server->run();

This should all be pretty self-explanatory; WsServer is an implementation of the more generic IoServer which communicates using web sockets, and we'll set it listening on port 8080. You're free to choose a different port, of course – provided it's not blocked by your firewall – but 8080 is usually a pretty safe bet.

Maintaining State

We'll let the server keep track of the current state of the games; no need to commit it to storage, we'll simply keep it in memory for optimum performance. Each time an event takes place in one of the games, we'll update the scores on the server and then broadcast the event to all listening clients.

First, though, we need to generate the fixtures (i.e. the list of games). For simplicity we'll do it at random, and just keep this set of fixtures active for the duration of the daemon's execution.

// src/LiveScores/Fixtures.php <?php namespace LiveScores; class Fixtures { public static function random() { $teams = array("Arsenal", "Aston Villa", "Cardiff", "Chelsea", "Crystal Palace", "Everton", "Fulham", "Hull", "Liverpool", "Man City", "Man Utd", "Newcastle", "Norwich", "Southampton", "Stoke", "Sunderland", "Swansea", "Tottenham", "West Brom", "West Ham"); shuffle($teams); for ($i = 0; $i <= count($teams); $i++) { $id = uniqid(); $games[$id] = array( 'id' => $id, 'home' => array( 'team' => array_pop($teams), 'score' => 0, ), 'away' => array( 'team' => array_pop($teams), 'score' => 0, ), ); } return $games; } }

Note that we're assigning each game a unique identifier, which we'll use later to indicate which game an event has taken place in. Going back to our Scores class:

// src/LiveScores/Scores.php public function __construct() { // Create a collection of clients $this->clients = new \SplObjectStorage; $this->games = Fixtures::random(); }

Because a client could call upon our widget at any stage during a game, it's important that they get up-to-the-minute information. One way to do this is simply to "reply" to a new connection request by sending the current state of the games, then rendering the list of games and their scores client-side.

Here's the onOpen implementation, which does just that:

// src/LiveScores/Scores.php public function onOpen(ConnectionInterface $conn) { // Store the new connection to send messages to later $this->clients->attach($conn); // New connection, send it the current set of matches $conn->send(json_encode(array('type' => 'init', 'games' => $this->games))); echo "New connection! ({$conn->resourceId})

"; }

Note that the message we're sending is actually a JSON object, with the type of event set as a property. There's no requirement to send messages using JSON – you can send any format you wish – but doing it in this way allows us to send different types of structured messages.

The HTML

Because we're going to load in the current scores over a web socket and render them using Javascript, the HTML for the page to start with is very simple:

<div id="scoreboard"> <table> </table> </div>

Once rendered, a row in the score-table will look like this:

<tr data-game-id="SOME-IDENTIFIER"> <td class="team home"> <h3>HOME TEAM NAME</h3> </td> <td class="score home"> <div id="counter-0-home"></div> </td> <td class="divider"> <p>:</p> </td> <td class="score away"> <div id="counter-0-away"></div> </td> <td class="team away"> <h3>AWAY TEAM NAME</h3> </td> </tr>

The counter-*-* elements are placeholders for a JS plugin we're going to use to render a fancy score widget later.

The JavaScript

Now let's start building the JS. The first thing to do is open a web socket:

var conn = new WebSocket('ws://localhost:8080');

You may need to substitute the hostname and / or the port number, depending on where your "server" is running.

Next, attach an event handler to the connection, which fires whenever a message is received:

conn.onmessage = function(e) {

The message itself is provided as a data property to the event e . Because we're sending messages in JSON format, we'll need to parse it first:

var message = $.parseJSON(e.data);

Now we can examine the type , and call the appropriate function:

switch (message.type) { case 'init': setupScoreboard(message); break; case 'goal': goal(message); break; }

The setupScoreboard function is pretty straightforward:

function setupScoreboard(message) { // Create a global reference to the list of games games = message.games; var template = '<tr data-game-id="{{ game.id }}"><td class="team home"><h3>{{game.home.team}}</h3></td><td class="score home"><div id="counter-{{game.id}}-home" class="flip-counter"></div></td><td class="divider"><p>:</p></td><td class="score away"><div id="counter-{{game.id}}-away" class="flip-counter"></div></td><td class="team away"><h3>{{game.away.team}}</h3></td></tr>'; $.each(games, function(id){ var game = games[id]; $('#scoreboard table').append(Mustache.render(template, {game:game} )); game.counter_home = new flipCounter("counter-"+id+"-home", {value: game.home.score, auto: false}); game.counter_away = new flipCounter("counter-"+id+"-away", {value: game.away.score, auto: false}); }); }

In this function we're simply iterating through the array of games, using Mustache to render a new row to be added to the scoreboard table, and instantiating a couple of animated counters for each one. The games array is going to store the current state of the games client-side, and includes references to those counters so we can update them as required.

Next up, the goal function. The message we receive over the web socket to indicate a goal will be a JSON object with the following structure:

{ type: 'goal', game: 'UNIQUE-ID', team: 'home' }

The game property contains the unique identifier, and team is either "home" or "away". Using these bits of information, we can update the relevant score in the games array, find the appropriate counter object and increment it.

function goal(message) { games[message.game][message.team]['score']++; var counter = games[message.game]['counter_'+message.team]; counter.incrementTo(games[message.game][message.team]['score']); }

All that remains is some way of indicating that a goal has been scored. In order to keep things simple, we'll just add that to the client; clicking a team's name will indicate that they've scored. In practice you'd have a separate application or page, but the principle is the same. We'll simply add a click handler as follows, which sends a simple JSON message over the web socket:

$(function () { $(document).on('click', '.team h3', function(e){ var game = $(this).parent().parent().attr('data-game-id'); var team = ($(this).parent().hasClass('home')) ? 'home' : 'away'; conn.send(JSON.stringify({ type: 'goal', team: team, game: game })); }); });

The server "listens" for these messages, and if it receives word of a goal it updates its record. All messages received are immediately re-broadcast to all connected clients.

// src/LiveScores/Scores.php public function onMessage(ConnectionInterface $from, $msg) { foreach ($this->clients as $client) { $client->send($msg); } $message = json_decode($msg); switch ($message->type) { case 'goal': $this->games[$message->game][$message->team]['score']++; break; } }

Finally, to get it up-and-running, you'll need to launch the server from the command-line:

php bin/server.php

That's it – try opening a couple of windows side-by-side, and clicking a team name to indicate a goal. You should see the scoreboard update straight away!

Conclusion

In this article, I've demonstrated a simple HTML and Javascript "live scores" widget using web sockets. It has its limitations; normally you'd expect to see the goalscorer and the time each goal was scored, as well as additional information such as bookings and sending-offs. However, because we're using a JSON object to represent an event, such features should be relatively straightforward to add. A live demo of this tutorial is available.

(Note: The Javascript and styles for the counters are thanks to Chris Nanney, and come from this post.)