/AJAX/ Building a web chat
13/07/2006 | Filed under Develop > AJAX / Develop > PHP

Paul Hudson finally climbs to the peak of mount Ajax, with the hardest project yet…
The advantage of being a monthly magazine is that we can assume you’ve had at least three weeks to catch your breath since the last session. And you probably need it: while AJAX is indeed the coolest new technology on the block, it takes an awful lot of hard work to combine it all with the abundance of PHP, SQL and JavaScript knowledge you have, while coming up with ways to monetise your skills. Well, we’re not here to provide you with a billionaire business plan for DotCom Bubble 2.0, but we can offer you a few of the skills you ought to be using if you’re hoping to create your own disruptive technologies.
Brace yourself as we’ve chosen a particularly hard topic: web chats. Even today, in the AJAX-enabled, web 1.9999 world, the most common way for customers to interact with service agents online is through a Java applet, or, worse, through an ActiveX application. These are hideously slow, limited, closed-source and usually fall into the wincingly-ugly category. Remedy this by writing your own chat applet, which you can do in a couple of hours. You can make it as fl exible as you like and you can fit it into your site’s design – all while honing your PHP skills.
But hold your horses: before we start coding we need to lay down a few quick ideas about what the chatroom should do. Do we want multiple people to chat in one room? Do we want to log all the chats? Do we want users to be able to provide their name? To give you the best chance of building the chat system that best fits your needs, we’re going to be implementing all three of those features, so that you can then pick and choose exactly what you want. To implement this we’ll need an AJAX and PHP front-end that receives user text and saves it to a database, as well as a script to read that database and send off any new chat messages to the clients. The clients will just be three web browsers (either IE or Firefox) chatting in a common room that we’ll call ‘lounge’. All set? Let’s go!
Copy the code:
Part 1, a live chat server in Javascript and PHP
We need five files to accomplish our chat system: one to display the current chat log for a user, one to check for new messages, one to fetch messages, one to send messages and another to handle the creation of an XMLHttpRequest object in a cross-platform manner. If you are on a crusade to keep the number of files to a minimum on your web server, you can integrate the checking/ fetching/sending files into one, distinguishing between them with a switch/case statement. The reason I’ve kept each operation separate here is because I’ve tried to keep the code as simple as possible so that you can clearly see how it works.
The first thing we need to do is define the database schemata we’re working with. We need a table to store each chatroom, because although we’re only going to be using one in our code it needs to be easy for you to add your own later. We also need a table to store chatroom messages, as well as storing the ID of the chatroom that each message was sent to. So, we need SQL like this:
CREATE TABLE chatrooms (ID
INT NOT NULL AUTO_INCREMENT
PRIMARY KEY, Name
VARCHAR(255));
CREATE TABLE chatroom_
messages (ID INT NOT NULL
AUTO_INCREMENT PRIMARY KEY,
ChatRoom INT, Username
VARCHAR(30), Message TEXT,
DateSent INT);
If you already have a user account system on your site, you should change the Username field to be an integer containing the user ID for each person sending a message. Each time a message is sent, we need to place it into the chatroom_messages table so that others can read it. To get our chat started, we need to create our ‘lounge’ chatroom, so run this SQL:
INSERT INTO chatrooms (Name)
VALUES ( Lounge );
Most of the code to handle our chatroom is stored in index.php, although this calls out to other scripts for the AJAX. As in previous issues, this script uses the ajax.js file to handle cross-browser compatibility, so we need to load that script right at the beginning of index. php. After that, it’s just a matter of creating and using three AJAX objects at the same time: one for checking whether any new messages have been posted; one to retrieve those new messages; and one to send our own messages. Checking which messages have changed is precisely the magic we need to make this project work. We don’t really want to have to download the entire chat log each time someone posts a small message. Instead, we should tell the server which was the last message we received, and let it send us all the more recent ones.
Anyway, let’s look at the actual code. First up is the checkChat() function:
function checkChat() {
if (check && check.
readyState < 4) return;
check = createAjax();
check.open( get ,
chatsession_check.
php?Room=1 );
check.onreadystatechange =
getCheck;
check.send( );
}
If you’ve read the last two issues on AJAX, the opening conditional statement should be the only surprising bit in that function. The rest sets us up an XMLHttpRequest object, points it at our message checking script (passing in the name of the room to check), and tells it to report the data back to the getCheck() function. The conditional statement is there to stop our script trying to check for messages before the previous check has completed. The readyState value of our AJAX object is set to four when it has finished its transaction and has data ready for us to read, so we want to stop our script checking for any messages while the ‘check’ AJAX object has a readyState lower than four, ie, it’s not ready to read.
In order for our chat session to be updated frequently, we need to set our browser to check the messages on a regular basis. As we’re not doing any difficult work with this check, there’s no harm in us checking it every five seconds or so, like this:
setTimeout(“checkChat()”,
5000);
We also need to call the checkChat() function as soon as the page loads, so you’ll see that the onload event for the <body> element has been set to call checkChat(). Moving on, we set the onreadystatechange event handler for our message checker to be getCheck(). This function needs to catch the output of the message check, compare it against the previous number we had, and then download new messages if necessary. Here’s how it looks:
function getCheck() {
clearTimeout(timerID);
timerID = setTimeout(“check
Chat()”, 5000);
if (isAjaxReady(check)) {
var thischeck = check.
responseText;
if (thischeck == 0)
return;
if (lastcheck == -1) {
lastcheck = thischeck;
return;
}
if (thischeck >
lastcheck) {
receive = createAjax();
if (receive) {
receive.open( get ,
chatsession_read.
php?Room=1&LastID=
+ lastcheck + &MaxID=
+ thischeck);
receive.
onreadystatechange =
getReceive;
receive.send( );
lastcheck = thischeck;
}
}
}
}
The first thing this function does is to clear the timer we’re using to check for new messages, then reset it. This is important: we’re using timers to check for messages, and we also want to check for messages when we send one (so that it updates properly), which means there’s the potential for a race condition. A race condition is when one function can be called and executed twice at the same time, and because the exact execution times are variables it’s also possible that the output – the results of the function – are variable. We want our functions to be reliable and predictable, which means we need to always clear the timer before doing anything, then re-set it so that it will execute in another five seconds from now.
Moving on, we need to exit out of the function if the value received back from our AJAX call is zero, which is a special value we’ll return from our message check script if there are no messages. If lastcheck (this stores the ID number of the last message that was posted) is equal to -1, then this is the first time we’re checking for new messages, so we need to set lastcheck to be the ID number of the newest message then exit. This tells our script where to start reading messages from. If this is our first check, it returns, but sets the first check number. Without this, people coming to our chatroom would automatically see the entire log of the chat so far, which is behaviour you may well want! Because the first AJAX call through checkChat() will always return (because lastcheck is set to -1 by default), we need to put a call to checkChat() in the onload attribute of our HTML’s <body> element. This ensures that our script grabs the latest message ID automatically as the page loads.
Now on to the main part of this function: if the thischeck variable is greater than the lastcheck variable, then we have new messages to read. This creates a new AJAX object to load the message reading script, passing in the last ID number we read (we don’t want that one again), the ID of the highest message that was available when we checked, plus the ID number of the chatroom we want to read.
If you’re wondering why we bother passing in the highest message ID, consider this: if we check for new messages and find that message 65 is the highest message available, then download all the messages, we’ll have everything from our last checked ID right down to message 65. But what happens if someone posts a message just seconds after we checked, but before we’ve downloaded the messages? In this scenario, our script will (wrongly) think that 65 was the latest message, and download message 66 twice. So, in this situation we take the safe route and read only as far as the message that was newest when we checked.
Finally, we update our lastcheck variable to be equal to the thischeck variable so that the new messages aren’t downloaded again.
Once the messages come back, our getReceive() function will get called, and all that has to do is print the text out on the screen because our PHP script has done all the HTML formatting for it. The function looks like this
function getReceive() {
if (isAjaxReady(receive)) {
var chatdiv = document.
getElementById( chat );
chatdiv.innerHTML =
chatdiv.innerHTML +
receive.responseText;
}
}
The HTML is using a <div> element with the ID of chat to store the chat log, and as you can see we append the new messages to the end of the existing chat log. If you wanted to have new messages appearing at the top, just reverse the last line.
Part 2, fixing the bugs
Despite having fewer than 150 lines of code, we already have two major bugs in our chat system: the first affects users sending messages, and the second affects users receiving messages, but both are easily fixed.
To see where the first problem crops up, try typing, “Are you really Tim O’Reilly?” as your message, then click Send. Now, on some web servers that will work just fine, but on others the message won’t appear in the log and won’t even appear in the database. The problem is that single quote in O’Reilly: we use single quotes to surround strings in our SQL queries, and having quotes inside quotes will break the query. To solve the problem, we need to replace all instances of ‘ with \’ The reason why this won’t fail on some servers is because PHP has a troublesome little option called magic_ quotes_gpc that will automatically replace ‘ with \’ for $_GET, $_POST and $_COOKIE input. That works just fine, but the problem is that it’s a setting – some people have it enabled, some have it disabled, so you never really know whether your script will work or not!
The best solution to this problem is to check whether magic quotes are enabled, and if they aren’t to add the slashes by hand. This check can be done through the get_magic_quotes_ gpc() function, which returns true if magic quotes is enabled. What we need to do is this:
if (!get_magic_quotes_gpc()) {
foreach($_GET as $var =>
$val) {
if (is_string($val))
$_GET[“$var”] =
trim(addslashes($val));
}
}
If you place that code before anything that inserts data into your database, you’ll be safe. The initial plans for PHP 6, which isn’t due out for quite some time, remove the magic_ quotes_gpc option entirely, forcing developers to slash their own quotes. This makes scripting harder for those who use magic_quotes_gpc, but for the rest of us it removes the worry of whether magic quotes are enabled or not.
The second problem lies in the text-based format of our messages and the fact that we print messages out without them first. Again, you can try this yourself: send the message “<h1>This site sucks!</h1>” and you’ll find that when the message comes back it gets interpreted as HTML so that the message is displayed nice and large. This is not what we want, so we have two options: remove HTML as each message comes in, or put the HTML into the database and strip it out before being sent to the clients as part of the chat log. You choose what suits you best, but my preferred method is to store the message with HTML intact then strip it out when it’s being displayed. I prefer this system because it’s possible that I may choose to allow some (or all) HTML at a later date, and this keeps my options open.
So, to accomplish this fix we need to modify chatsession_read.php to this:
if (mysql_num_rows($result))
{
while ($r = mysql_fetch_
assoc($result)) {
extract($r, EXTR_PREFIX_
ALL, “msg”);
$msg_Username = strip_
tags($msg_Username);
$msg_Message = strip_
tags($msg_Message);
echo “<strong><$msg_
Username></strong>
$msg_Message<br />”;
}
}
Bookmark with:
Comments
dts / 11/08/2006 / 21:50 / http://www.jobs.co.in
I have to use three levels of quotes in xml tag. Any idea how to do this?
anthony gandy / 23/09/2006 / 13:04
I would like to start a chat line or some type of dot com thing but it seems like u have gotten to be a computer freak.So is there any mags or books i can buy so a computer dummy like myself would understand better how to start this up.
Adityo / 20/11/2006 / 12:20
where can I get chatsession_check.php example?
Dumitru / 28/12/2006 / 14:19
It'll be a very bad chat!
TCP/IP protocol is not very fast...
:)
ssh / 03/04/2008 / 10:07 / http://www.scheinschatten.de
That's a really nice solution wich i can use for one of my web sites. I tried some of those web chats where you just have to implement some code and it's ready. But they are so unflexible. May be this will cost me some brain cells - but i think it can get done. thanks
anon / 30/08/2009 / 18:07
This is probably the worst solution ever for the use of AJAX. Has anyone ever seen the memleaks created from successive AJAX polls. Ludicrous! If you're going to bang out a 3rd rate chat client just do it the old fashioned HTTPD polling route through an IFRAME. Failing that, stop promoting pointless techniques that should never be implemented in the first place. Build an XML socket server (they're simple enough) or invest in dedicated comms server such as FMS or Red5. And if your budget wont stretch to either learning to program an XML socket or your wallet cant stretch to FMS, hit it from the AMF route. Just please - I emplore you - dont use AJAX for this. AJAX should be used for simple processes, not encouraging someone to kill what memory they have left to sit and chat for 30 minutes while some bozo in Nigeria tries to hit them for a 419'er scam.
I suggest people learn the correct methodology - not how to turn AJAX buzz to a short term advantage.
The Flex SDK is free to download, and with a little bit of a learning curve you could have yourself a working chat in next to no time. Additionally, if a socket server seems daunting or FMS is way beyond your novice budget, the poll the thing through AMF.
AJAX for a chat client is anywhere near a "nice solution". It's exploiting a technology for which is certainly was never intended.
Ram / 28/11/2009 / 10:08
I fail to understand the need to store the messages in database. Can't they be handled via sockets or ports. I mean everytime intereacting with database wil have performance issue when we have 1000 users online. I could be incorrect but this are my views.



