ASPit - Totally ASP JSit - Totally JavaScript
Search PHPit

Use this textbox to search for articles on PHPit. Seperate keywords with a space.

Advertisements

Creating a chat script with PHP and Ajax, Part 2

(Page 4 out of 5)

Ping? Pong!

As I've already explained, a special function called ping() will run every second, and this is the function that will:
- let the chat server know that the user is still in the chatroom
- request the updated user list
- request any new messages

It's actually a very simple function, and all it really does is send a Ajax request, like so:

function ping() {
        // Send ping, to let the server know I'm still here
        var url = base_url + 'ping.php';
        var pars = creds();

        var ajax = new Ajax.Request(url, {method: 'post', parameters: pars, onComplete: handle_ping});

        Logger.info("Sent Ping: " + pars);
}

This request is then received by the ping.php file, which actually does all the magic. It first authenticates the user, like so:


include ('includes/global.php');

// Authenticate user
$user = auth_user();

Using the auth_user() function, which is located in 'includes/user-functions.php' and looks like this:

function auth_user {
        global $db;

        // Get vars
        $username = r('username');
        $password = r('password');
        $lastactive = r('lastactive');

        if (empty($username) OR empty($password) OR empty($lastactive)) {
                return_error('Invalid user. Please logout and try again.', 10);
                die();
        }

        // Authenticate user
        $user = $db->GetRow("SELECT userid, username, password, lastactive FROM user WHERE username = ? AND password = ?", array($username, $password));

        if ($user == false) {
                return_error('Invalid user. Please logout and try again.', 10);
                die();
        }

        return $user;
}

As you can see, the auth_user() function is a really basic function that simply checks the username and password of the user.

The ping.php file then continues to send the new user list, like so:

// Send new user list
send_userlist();

Again using a separate function, called send_userlist(), found in includes/user-functions.php, which looks like this:

function send_userlist() {
        global $db;

        // Remove any inactive users (no ping for 15 seconds)
        remove_inactive_users();

        // Get userlist
        $userlist = $db->GetAll("SELECT userid, username FROM `user`");

        // Queue command
        json_queue_add ('add_userlist', array('userlist' => $userlist));

        return true;
}

The most important part in this function is the second last line, on which the json_queue_add() function is used. This is a special function that is used to queue different JSON commands, with different arguments, and when the json_queue_send() function is called, all the JSON commands are sent at once. This allows us to send multiple instructions back to the client at once. These functions look like this:

function json_queue_add ($func, $args) {
        $funcs =& $GLOBALS['_json_queue'];

        // Add to commands array
        $funcs[] = array('func' => $func, 'args' => $args);
}

function json_queue_send {
        $funcs =& $GLOBALS['_json_queue'];
        ob_clean_all();

        $data = array('queue' => $funcs);

        return_raw_json($data);
}

Getting back to the ping.php file, after sending the new user list, it sends any new messages, using the send_messages() function:

function send_messages($user) {
        global $db;

        $lastactive = $user['lastactive'];

        // Get all new messages since last active
        $messages = $db->GetAll("SELECT username, time, message, token FROM message WHERE time >= ?", array($lastactive));

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

        // Queue command
        json_queue_add ('add_messages', array('messages' => $messages));
}

It then updates the last active timestamp of the user, and finally sends all the commands with the json_queue_send() function.

Now that the ping.php file has sent all the commands, it's back to the JS file, as it needs to handle the response.

The handle_ping() function, which is used as the callback for the request (see the ping() function), begins with the basic stuff:

function handle_ping (request) {
        var data = request.responseText;

        Logger.info("Ping Received: " + data);

        // Transform JSON
        data = JSON.parse(data);

        if (data == false) {
                // Not JSON
                Logger.error("Invalid Response", "Not a JSON structure");
                return false;
        }

        if (typeof(data.error) != 'undefined') {
                alert (data.error.msg);
                return false;
        }

        if (typeof(data.queue) == 'undefined') {
                Logger.error("Invalid Response", "Not a queue");
                return false;
        }

As you can see in the above example, it makes sure that a valid JSON response has been sent, and that an actual command queue was sent.

After that the function needs to handle all the different commands. Seeing as we only have three commands yet, it's rather simple:

data.queue.each( function(item) {
                var func = item.func;
                var args = item.args;

                // Determine function
                switch(func) {
                        case 'add_userlist':
                                add_userlist (args.userlist);
                                break;
                        case 'update_lastactive':
                                lastactive = args.lastactive;
                                break;
                        case 'add_messages':
                                add_messages (args.messages);
                                break;
                }
        });
}

We loop through the queue, and use a switch statement to check which command we're dealing with. In each case, we do something different, for example when we're dealing with the 'update_lastactive' command, the lastactive variable is set to the updated value.

In the above example you will see that a function called 'add_messages' is used to add any new messages. This function looks like this:

function add_messages(messages) {
        var div = $('messages');

        messages.each( function(message) {
                // Make sure to filter out duplicate messages
                var dup = message.time + '|' + message.token;
                if (dup_filter.inArray(dup) == true) { return; }
                add_dup_filter(dup);
               
                var new_b = document.createElement('b');
                new_b.innerHTML = message.username + '> ';
                div.appendChild(new_b);

                var new_span = document.createElement('span');
                new_span.innerHTML = message.message.escapeHTML();
                div.appendChild(new_span);

                div.appendChild(document.createElement('br'));
        });

        div.scrollTop = div.scrollHeight;
}

It basically loops through all the new messages, and then adds them (by inserting new HTML elements in the chat div). But before adding them, a check is made to make sure duplicate messages aren't added.

Because of the way Ajax and the internet works, it's very easy to receive the same new messages multiple times, which means they'll show up multiple times. To prevent this from happening, each message has a unique token, and this is used to make sure that duplicate messages aren't added twice.

This is basically how everything in the chat room is constantly updated, by sending a ping request every second, and then receiving new data back. Let's have a look at the final part of the chat room: sending messages.

« Previous: Creating the login
Next: Sending new messages »



8 Responses to “Creating a chat script with PHP and Ajax, Part 2”

  1. AjaxBlog.it » Creare una chat con PHP ed Ajax Says:

    […] Tra le moltissime letture, mi ero dimenticato di far presente del primo di una serie di articoli dedicati alla creazione di una chat utilizzando il linguaggio di programmazione PHP ed ovviamente Ajax. Be’, lo faccio ora insieme all’annuncio dell’uscita della seconda parte. […]

  2. Matthijs Says:

    Very interesting article. Will certainly download the code and study it some more. Thanks.

    One question though: how is the escaping of the data to the db handled? I didn’t study all code yet, but in your article in the login script there’s only the code
    // Make it safe
    $username = htmlentities($username);
    before the data is sent to the db.
    Does ADODB handle the escaping itself?

  3. Bernhard Froehlich Says:

    It’s a good example for what can be done with Ajax but it’s no real world example. Webchats are supposed to serve at least 1000 Clients but this thing will kill the Server if there are more than ~100 and i testet the live demo - it’s having a lag of about 2sec and thats not good in real world.

    But nevertheless a nice example.

  4. berhard kuisper Says:

    Its realy good chat but this is php. I code java chats and i have mor than 2000 users in this system , but with php server was slow or down.

    Sorry java is greater for chatsystems or php for a alternativ session!

    Best regards

  5. Aaron Hancock Says:

    Bernhard, I think the delay is due to the refresh/reload rate and not server lag. With a few tweaks, this would make a great small scale chat client.

  6. www.stuffvideo.com » Ajax article Says:

    […] http://phpit.net/article/ajax-php-chat-part-two/1/ […]

  7. Michael Says:

    There are some Problems with UTF-8 with German Umlaute (ä ü ö). How can i handle this?

  8. Prasanna Says:

    Hi

    This is a gr8!!!!!!!!!! article…

    Will download the code to dwell deep into it.

    Thanks
    Prasanna

Leave a Reply

About the author
Dennis Pallett is the main contributor to PHPit. He owns several websites, including ASPit and Chill2Music. He is currently still studying.
Article Index
  1. Introduction & Structure
  2. Basic Necessities & Chat Client
  3. Creating the login
  4. Ping? Pong!
  5. Sending new messages
Bookmark Article
Download Article
PDF
Download this article as a PDF file