0

I run a server on a shared hosted space (Aruba) with LAMP configuration, with two separate sets of PHP pages, one for the administrator and one for several clients (imagine a quiz game where the administrator submits questions).

I want to achieve this behaviour:

  • The administrator presses a button and released the question
  • At any time within ten seconds (or even immediately, but not strictly requested) all the clients must display AT THE SAME TIME the page with the text of the question.

To achieve this, I thought of different solutions:

  • Web sockets (not feasible, as I cannot install server components on my web page)
  • Trigger file generated by the administrator; the clients will periodically (~10 sec) poll (setInterval()) for the presence of this file and, depending on the creation time of the file (or an equivalent timestamp read from the file name or file content) the client will start a countdown (setTimeout()) for the time remaining to when the new page has to be fired, to make sure that all clients eventually trigger at the same time (tenth of second)
  • Trigger via database (basically same as trigger file, but possibly slower).

I tried the second solution (to manage the reading of trigger file on client-side) both in PHP and in Javascript, but they both fail when there is more than a client connected:

  • PHP apparently fails because Apache does not support many simultaneous threads and gets somehow stuck
  • Javascript somehow occasionally misses to recognize the presence of the file in the local directory (with more than one client connected XMLHttpRequest.status incorrectly returns 404 even when the trigger file is there) - I even created separate trigger files for the different clients, to make sure there are no concurrency conflicts.

Any hints on why XMLHttpRequest.status occasionally fails, or advice on a better way of achieving this behaviour?

Thank you in advance.

2 Answers2

0

Have you considered long polling? See https://github.com/panique/php-long-polling for an example of how to do this with PHP. This will not scale well because of the number of apache and php processes that would have to stay active, but would be fine for a few clients. If you need it to scale then I would consider switching server technologies to something like hack (like PHP; see http://hacklang.org/) or node which is great at this kind of thing.

Vic Metcalfe
  • 121
  • 1
  • 2
  • I had considered **long polling**, together with the more efficient **Server Sent Events**: both these technologies guarantee _push behaviour_ (that is immediate response on the client when something changes on server side), but not _synchronicity_: in fact, with this solution, every client will create a _separate instance_ of the PHP script on the server, and then the problem would simply be shifted to syncing these several PHP server-side scripts. – Marco Motogordon Apr 21 '15 at 12:36
  • By the way I did some tests on the suggested example https://github.com/panique/php-long-polling: on my Aruba web server it works well with one client connected, but when two (or, worse, three) browsers are simultaneously connected the notifications are significantly delayed, and it gets totally stuck with four or more. Therefore I'd say I need to find another workaround to achieve my expected behaviour. – Marco Motogordon Apr 21 '15 at 16:38
0

EDIT: I didn't understand the question fully in my original answer. Here is my refined answer:

With the current limitations you are under, I see only one way of achieving a simultaneous server response. First, you will need to implement HTML5 SSE (server side events). When your server is ready to send a message to the clients, trigger an SSE to be sent to each client. This event does not need to send any data so there's no need for clients being contacted simultaneously. The event tells the clients to execute an ajax call to your php ajaxHandler.

During each ajax call from the client, your server will check your database for the value of 'waitingClients' in some table you created. If the value is 0, set the value to 1. If the value is greater than 0, increment the waitingClients value by 1. After each ajax call increments the database value, the individual ajax calls are then suspended in a while loop until 'waitingClients' is equal to the value of 'totalClients'. I recommend that you create some kind of entry in your database that records the number of active clients. This makes your 'totalClients' value more dynamic.

You may run into problems with the ajax calls timing out after 30 seconds. Since you're only returning database values, I doubt that you'll run into this problem unless something happens with a client's connection hanging.

Here is some example code (untested):

HTML

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <script src="jquery-1.9.1.min.js"></script>
    <script src="ajaxTest.js"></script>
</head>
<body>
    <div id="server_message">Waiting for server response</div>
</body>
</html>

Ajax:

$(function() {
   var message = $('#server_message');
    $.ajax({
        url: 'yourAjaxHandler.php',
        type: 'POST',
        data: {
            getAnswer: true
        },
        success: function(response) {
            console.log(response);
            message.text(response);
        }
    })
});

PHP Ajax handler

<?php

$host = 'db host address';
$dbname = 'your database name';
$username = 'your username';
$password = 'your password';
$conn = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);

// Define expected number of total clients. I would recommend having clients log an entry into the database upon initial login/connection.
// This would make tallying the number of clients more dynamic. Otherwise you will always need 4 clients connected
$totalClients = 4;

if (isset($_REQUEST['getAnswer'])) {

    $qry = 'SELECT waitingClients from some_table';
    $waitingClients = $conn->query($qry);

    if ($waitingClients === 0) {
        // Create waitingClients in database if it doesn't exist. Otherwise, increment value to 1
        $qry = "UPDATE some_table set waitingClients = 1";
        $conn->exec($qry);
    } else {
        // Increment waitingClients
        $qry = "UPDATE some_table set waitingClients = waitingClients + 1";
        $conn->exec($qry);
    }

    while ($waitingClients <= $totalClients) {
        // The while loop will keep the ajax call active for all clients
        // Keep querying database until waitingClients value in DB matches the number of totalClients
        $qry = 'SELECT waitingClients from some_table';
        $waitingClients = $conn->query($qry);
    }

    // Set the value of waitingClients back to 0
    $qry = "UPDATE some_table SET waitingClients = 0";
    $conn->exec($qry);

    // Return your server message to the clients
    echo json_encode("Your server message"); // You could also store your server message in the database
}
Walker Boh
  • 750
  • 6
  • 13
  • I am not sure I understood your proposed solution. In my scenario I have _one_ PHP page where the game master hits a button and unleashes the question, and a separate (but _unique url_) PHP or Javascript page accessed by multiple players (one instance for each) where the available answers are displayed. The clients (players) are ready by default, as their page is always launched before the master submits the question. – Marco Motogordon Apr 21 '15 at 15:44
  • I see. That clarifies your question a bit. Then in your case, you would want to look at long polling. But I see from your response to Vic Metcalfe that you were experiencing issues with synchronous responses due to a delay with the web server. If you don't have the capacity to change your web server or use web sockets, I would suggest you look into HTML5 server side events. I don't know much about them but it seems to be relevant to the route you're going: http://www.w3schools.com/html/html5_serversentevents.asp and also http://www.html5rocks.com/en/tutorials/eventsource/basics/ – Walker Boh Apr 21 '15 at 16:53
  • Thank you Walker. Actually I had already evaluated SSE and discarded it (see my previous comment to Vic). Since even Javascript seems to have concurrency issues, I am afraid I have to completely change technology. – Marco Motogordon Apr 22 '15 at 08:17
  • I don't know how limited you are with your server but if you can at least install a php extension, look into APC cache. http://php.net/manual/en/book.apc.php . It would allow clients to access shared variables. If that's not possible, I can only think that a database is your solution. The reason you experienced a slow down when using a file is due to file locking. You shouldn't have that problem with a database and it will be much faster. – Walker Boh Apr 22 '15 at 14:36
  • With the database route, create an entry that counts the number of clients waiting for a response if it doesn't exist. If it exists, increment the count. After you increment the count, execute a while loop that constantly checks the client count in the database until the count is equal to the total number of connected clients. After the loop, return your message to the clients and remove the client count entry from your database. – Walker Boh Apr 22 '15 at 14:38
  • And last but not least, I would not recommend this approach but if you are desperate, you can look into overwriting session behavior to allow you to have persistent session variables across multiple clients. It's hackish but it's been done – Walker Boh Apr 22 '15 at 14:39
  • I do have a database that I can access, but I believe that the issue is not due to file locking (the files are only accessed for _READ_ by the clients, and only for a minimal fraction of a second), but to the multiple PHP script instances that simultaneously need to be supported by the web server Apache (this is why I had thought of switching the computation on browser side via Javascript). Unfortunately, as I am on a shared hosted space, I cannot install PHP extensions nor replace Apache with more efficient webservers. – Marco Motogordon Apr 22 '15 at 16:44
  • I may have misread this answer then, but you may find it helpful: http://stackoverflow.com/a/1430890/2466782 . Also, if you have database access, you could try the approach I recommended. SSE tells clients to send ajax call to server > During each clients ajax call, increment 'clientCount' entry in the database on the server side. Then in a while loop, check clientCount until it reaches the total number of connected clients and return your server message to the client. If I think of better suggestions, I'll let you know. I know it can be a pain working within such limitations – Walker Boh Apr 22 '15 at 18:25
  • I refined my original answer – Walker Boh Apr 22 '15 at 19:45