views:

140

answers:

3

Im working on a simple chat app, probably 10 to 20 users per room.

The Script that queries the database for new messages looks too simple for all the request it'll be getting.

Below is the block of code that loops for new messages, the rest of the script is just getting the variables, construction of the query and the json response object:

$sleepTime = 1; //Seconds
$data = "";
$timeout = 0;

//Query database for data
while(!$data and $timeout < 10){
    $data = getQuery($sql);
    if(!$data){
        //No new messages on the chat
        flush();
        //Wait for new Messages
        sleep($sleepTime);          
        $timeout += 1;
    }else{
        break;
    }
}

The block above will query the database for new messages every second for 10 seconds, if after the 10 seconds there are no new messages it will notify the browser. The browser waits 5 seconds and then sends another request to get new messages.

However If the script finds new messages, the browser will request more new messages instantly as soon as it gets the response with the new messages from the server.

This process goes on and on...

So how can i optimize this process any further? Is this as good as it gets? Is working fine on my local server, but im afraid that just a few users could overload a live server(shared host) with all the requests and the loopings.

Here is live DEMO you can check with firebug http://pixbush.com/chat/chat.php

+1  A: 

This screams for AJAX.

See my post today on how to send JavaScript responses to PHP. There's no reason why your script should have to loop at all.


EDIT: My bad about the AJAX. When I wrote the IRC chatbot PHP-Egg, I ran into this problem * 100. The way I solved it (back in the PHP 4 days, mind you) was to pcntl_fork() PHP and have it simply return each time there was a message. The benefits are that it doesn't 100% block the CPU, unlike sleep() and is MUCH faster than 10 seconds or any arbitrary limit you put on it.


I'm revising my answer again (sorry!):

Use some sort of asynchronous process that dumps the text into a file.

Then what you'd do is

if (filemtime('chat.log') > time() - 5) { echo json_encode(file_get_contents('chat.log')); }

Benefits: Limited SQL usage; no need to loop.

hopeseekr
I'm pretty sure the OP *is* using AJAX together with **long-polling**, as the question states. http://en.wikipedia.org/wiki/Comet_(programming)#Ajax_with_long_polling
deceze
@Pablo: A few tutorials which may help you here: http://css-tricks.com/chat2/, http://net.tutsplus.com/tutorials/javascript-ajax/how-to-create-a-simple-web-based-chat-application/, http://anantgarg.com/2009/05/13/gmail-facebook-style-jquery-chat/. AJAX is certainly the way to go.
Lucanos
im using AJAX, Plus im Looping in the server to minimize the AJAX requests.
Pablo
@Lucanos, thanks for the references. I came across all of those projects in my research before i started building my own because they are missing some features i'm interested on.
Pablo
@Pablo: (Just educated myself on long-looping.) Is there any specific characteristic in your expected visitor/usage profile which made you go with the looping solution? I get that it would reduce AJAX requests, but it also means that the server could be tied up with a number of concurrent visitors all waiting in loops (which may cause just as much of an issue for visitors, I would think).
Lucanos
@hopeseekr thanks i'll look into pcntl_fork()
Pablo
@Lucanos: sleep() is actually a CPU-blocking operation (at least it was in PHP 4 days when I last attempted what @Pablo is trying). Meaning that if you have 100 users, that could easily lead to 100% CPU just on loops.
hopeseekr
@hopeseekr: Kinda figured that might be the case, hence the follow-up question asking Pablo the goals of using this long-loop functionality.
Lucanos
A: 

You could try using files labeled according to conversationId instead of a DB and just check if the file has been 'touched'. Also, use usleep and set_time_limit(for windows server) to set your interval in milisecs and increase the sleep time. Usleep actually, delays CPU usage but is still fires instantly incase the file has been changed.

Here is a section of my chat script. =)

define('SUCCESS', '__SUCCESS__');
define('FAILED', '__FAILED__');

$tmpLib = $TMPFOLDER;
$msgPath = $MSGFILE;

$timeout = $POLLSPEEDSEC;

$acct = new Account($tmpLib, $_GET['key']);

if (false === $acct) {
    return false;
}

$msg = new Message($msgPath, $acct);

$lastMod = !empty($_GET['ts']) ? $_GET['ts']: 0;
$lastMod = substr($lastMod, 0, 10);
$lastMod = (int)$lastMod;

$result = array();

$start = gettimeofday();
$prevMsg = $acct->getTemp('cache');

do{
    usleep(10000);

    if ($acct->getFileTime() >= $lastMod) {
        $result['account'] = $acct->getAllOnline();
    }

    if($msg->getFileTime() >= $lastMod) {
        $result['message'] = $msg->fetch();
    }

    if (!empty($result)) {
        $theMsg = json_encode($result);
        if ($theMsg != $prevMsg) {
            $acct->setTemp('cache', $theMsg);
            echo $theMsg;
            flush();
            exit;
        }
        $result = array();
        $lastMod = time();
    }

    $end = gettimeofday();
} while($timeout > ($end['sec'] - $start['sec']));

echo FAILED;
sheeks06
Same as my answer, so I agree.
hopeseekr
@sheeks06 and how exactly do you make sure file does't get corrupted by been access simultaneously and possibly been written to at the same time?
Pablo
For simultaneous access, FS locks the files not corrupt it. If this happens, no change will be done. There is maybe 1 out of 50 chances that this will happen. The chance is too low to make it a show-stopper. Plus, same issue also happens if you use a DB. Files are better, in theory, because it "releases" the lock and respond faster than DB's.
sheeks06
A: 

From your description, it sounds like you have a 5 second gap of silence which defeats the benefit of long-polling. Have the browser start another request immediately when a call returns (long or short) from the server. As a back up, with each server call, have the browser start a timeout slightly longer than the server-side timeout, but cancel it when a request is returned. If the server request ever fails and the browser timeout completes, start a new request.

webnician