How to Make a Chat Room Script with PHP

How to make a chat room script with PHP?

there are (roughly) 3 options for creating a chat application:

sockets

use flash/java and sockets for the frontend and a socket-capable programming language for the backend. for the backend, i'd recommend java or python, because they are multithreading and NIO-capable. it's possible to do it with PHP (but php can't really do efficient multithreading and is generally not really suited for this). this is an option if you need high performance, and probably not what you're looking for.

use ajax and pull

in this case all clients are constantly (for example ever 2 seconds) polling if something new has happened. it feels strange because you only get responses at those intervals. additionally, it puts quite a strain on your server and bandwidth. you know an application uses this technique because the browser constantly refreshes. this is a suboptimal solution.

use ajax and push

this works with multipart-responses and has long running (php-) scripts in the backend. not the best solution, but most of the time it's better than pulling and it works and is used in several well known chat apps. this technique is sometimes called COMET.

my advise: if you need a chat app for production use, install an existing one. programming chat applications is not that easy.

if you just want to learn it, start with a simple ajax/pull app, then try to program one using ajax and push.

and yes, most probably you'll need a database, tough i successfully implemented a very simple ajax/pull solution that works with text files for fun (but i certainly wouldn't use it in production!).

it is (to my knowledge, but i'm pretty sure) not possible to create a chat app without a server-side backend (with just frontend javascript alone)!

UPDATE

if you want to know how the data pushing is done, look at the source here: http://wehrlos.strain.at/httpreq/client.html. async multipart is what you want :)

function asSendSyncMulti() {
var httpReq = new XMLHttpRequest();

showMessage( 'Sending Sync Multipart ' + (++this.reqCount) );

// Sync - wait until data arrives
httpReq.multipart = true;
httpReq.open( 'GET', 'server.php?multipart=true&c=' + (this.reqCount), false );
httpReq.onload = showReq;
httpReq.send( null );
}

function showReq( event ) {
if ( event.target.readyState == 4 ) {
showMessage( 'Data arrives: ' + event.target.responseText );
}
else {
alert( 'an error occured: ' + event.target.readyState );
}

}

showReq is called every time data arrives, not just once like in regular ajax-requests (i'm not using jquery or prototype here, so the code's a bit obese - this is really old :)).

here's the server side part:

<?php

$c = $_GET[ 'c' ];

header('Content-type: multipart/x-mixed-replace;boundary="rn9012"');

sleep( 1 );

print "--rn9012\n";
print "Content-type: application/xml\n\n";
print "\n";
print "Multipart: First Part of Request " . $c . "\n";
print "--rn9012\n";
flush();

sleep( 3 );

print "Content-type: application/xml\n\n";
print "\n";
print "Multipart: Second Part of Request " . $c . "\n";
print "--rn9012--\n";

?>

update2

regarding the database: if you've got a nothing-shared architecture like mod_php/cgi in the backend, you definitley need some kind of external storage like databases or textfiles. but: you could rely on memory by writing your own http server (possible with php, but i'd not recommend it for serious work). that's not really complicated, but probably a bit out of the scope of your question ^^

update3

i made a mistake! got everything mixed up, because it's been a long time i actually did something like that. here are the corrections:

  1. multipart responses only work with mozilla browsers and therefore are of limited use. COMET doesn't mean multipart-response.

  2. COMET means: traditional singlepart response, but held (with an infinite loop and sleep) until there is data available. so the browser has 1 request/response for every action (in the worst case), not one request every x seconds, even if nothing response-worthy happens.

How to implement a chat room using Jquery/PHP?

Chat with PHP/AJAX/JSON

I used this book/tutorial to write my chat application:

AJAX and PHP: Building Responsive Web Applications: Chapter 5: AJAX chat and JSON.

It shows how to write a complete chat script from scratch.


Comet based chat

You can also use Comet with PHP.

From: zeitoun:

Comet enables web servers to send data to the client without having any need for the client to request it. Therefor, this technique will produce more responsive applications than classic AJAX. In classic AJAX applications, web browser (client) cannot be notified in real time that the server data model has changed. The user must create a request (for example by clicking on a link) or a periodic AJAX request must happen in order to get new data fro the server.

I'll show you two ways to implement Comet with PHP. For example:

  1. based on hidden <iframe> using server timestamp
  2. based on a classic AJAX non-returning request

The first shows the server date in real time on the clients, the displays a mini-chat.

Method 1: iframe + server timestamp

You need:

  • a backend PHP script to handle the persistent http request backend.php
  • a frondend HTML script load Javascript code index.html
  • the prototype JS library, but you can also use jQuery

The backend script (backend.php) will do an infinite loop and will return the server time as long as the client is connected.

<?php
header("Cache-Control: no-cache, must-revalidate");
header("Expires: Sun, 5 Mar 2012 05:00:00 GMT");
flush();
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
<title>Comet php backend</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>

<body>
<script type="text/javascript">
// KHTML browser don't share javascripts between iframes
var is_khtml = navigator.appName.match("Konqueror") || navigator.appVersion.match("KHTML");
if (is_khtml)
{
var prototypejs = document.createElement('script');
prototypejs.setAttribute('type','text/javascript');
prototypejs.setAttribute('src','prototype.js');
var head = document.getElementsByTagName('head');
head[0].appendChild(prototypejs);
}
// load the comet object
var comet = window.parent.comet;
</script>

<?php
while(1) {
echo '<script type="text/javascript">';
echo 'comet.printServerTime('.time().');';
echo '</script>';
flush(); // used to send the echoed data to the client
sleep(1); // a little break to unload the server CPU
}
?>
</body>
</html>

The frontend script (index.html) creates a "comet" javascript object that will connect the backend script to the time container tag.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Comet demo</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="prototype.js"></script>

</head>
<body>
<div id="content">The server time will be shown here</div>

<script type="text/javascript">
var comet = {
connection : false,
iframediv : false,

initialize: function() {
if (navigator.appVersion.indexOf("MSIE") != -1) {

// For IE browsers
comet.connection = new ActiveXObject("htmlfile");
comet.connection.open();
comet.connection.write("<html>");
comet.connection.write("<script>document.domain = '"+document.domain+"'");
comet.connection.write("</html>");
comet.connection.close();
comet.iframediv = comet.connection.createElement("div");
comet.connection.appendChild(comet.iframediv);
comet.connection.parentWindow.comet = comet;
comet.iframediv.innerHTML = "<iframe id='comet_iframe' src='./backend.php'></iframe>";

} else if (navigator.appVersion.indexOf("KHTML") != -1) {

// for KHTML browsers
comet.connection = document.createElement('iframe');
comet.connection.setAttribute('id', 'comet_iframe');
comet.connection.setAttribute('src', './backend.php');
with (comet.connection.style) {
position = "absolute";
left = top = "-100px";
height = width = "1px";
visibility = "hidden";
}
document.body.appendChild(comet.connection);

} else {

// For other browser (Firefox...)
comet.connection = document.createElement('iframe');
comet.connection.setAttribute('id', 'comet_iframe');
with (comet.connection.style) {
left = top = "-100px";
height = width = "1px";
visibility = "hidden";
display = 'none';
}
comet.iframediv = document.createElement('iframe');
comet.iframediv.setAttribute('src', './backend.php');
comet.connection.appendChild(comet.iframediv);
document.body.appendChild(comet.connection);

}
},

// this function will be called from backend.php
printServerTime: function (time) {
$('content').innerHTML = time;
},

onUnload: function() {
if (comet.connection) {
comet.connection = false; // release the iframe to prevent problems with IE when reloading the page
}
}
}
Event.observe(window, "load", comet.initialize);
Event.observe(window, "unload", comet.onUnload);

</script>

</body>
</html>

Method 2: AJAX non-returning request

You need the same as in method 1 + a file for dataexchange (data.txt)

Now, backend.php will do 2 things:

  1. Write into "data.txt" when new messages are sent
  2. Do an infinite loop as long as "data.txt" file is unchanged
<?php
$filename = dirname(__FILE__).'/data.txt';

// store new message in the file
$msg = isset($_GET['msg']) ? $_GET['msg'] : '';
if ($msg != '')
{
file_put_contents($filename,$msg);
die();
}

// infinite loop until the data file is not modified
$lastmodif = isset($_GET['timestamp']) ? $_GET['timestamp'] : 0;
$currentmodif = filemtime($filename);
while ($currentmodif <= $lastmodif) // check if the data file has been modified
{
usleep(10000); // sleep 10ms to unload the CPU
clearstatcache();
$currentmodif = filemtime($filename);
}

// return a json array
$response = array();
$response['msg'] = file_get_contents($filename);
$response['timestamp'] = $currentmodif;
echo json_encode($response);
flush();
?>

The frontend script (index.html) creates the <div id="content"></div> tags hat will contains the chat messages comming from "data.txt" file, and finally it create a "comet" javascript object that will call the backend script in order to watch for new chat messages.

The comet object will send AJAX requests each time a new message has been received and each time a new message is posted. The persistent connection is only used to watch for new messages. A timestamp url parameter is used to identify the last requested message, so that the server will return only when the "data.txt" timestamp is newer that the client timestamp.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Comet demo</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript" src="prototype.js"></script>
</head>
<body>

<div id="content">
</div>

<p>
<form action="" method="get" onsubmit="comet.doRequest($('word').value);$('word').value='';return false;">
<input type="text" name="word" id="word" value="" />
<input type="submit" name="submit" value="Send" />
</form>
</p>

<script type="text/javascript">
var Comet = Class.create();
Comet.prototype = {

timestamp: 0,
url: './backend.php',
noerror: true,

initialize: function() { },

connect: function()
{
this.ajax = new Ajax.Request(this.url, {
method: 'get',
parameters: { 'timestamp' : this.timestamp },
onSuccess: function(transport) {
// handle the server response
var response = transport.responseText.evalJSON();
this.comet.timestamp = response['timestamp'];
this.comet.handleResponse(response);
this.comet.noerror = true;
},
onComplete: function(transport) {
// send a new ajax request when this request is finished
if (!this.comet.noerror)
// if a connection problem occurs, try to reconnect each 5 seconds
setTimeout(function(){ comet.connect() }, 5000);
else
this.comet.connect();
this.comet.noerror = false;
}
});
this.ajax.comet = this;
},

disconnect: function()
{
},

handleResponse: function(response)
{
$('content').innerHTML += '<div>' + response['msg'] + '</div>';
},

doRequest: function(request)
{
new Ajax.Request(this.url, {
method: 'get',
parameters: { 'msg' : request
});
}
}
var comet = new Comet();
comet.connect();
</script>

</body>
</html>

Alternatively

You can also have a look at other chat applications to see how they did it:

  • http://hot-things.net/?q=blite - BlaB! Lite is an AJAX based and best viewed with any browser chat system that supports MySQL, SQLite & PostgreSQL databases.

  • Gmail/Facebook Style jQuery Chat - This jQuery chat module enables you to seamlessly integrate Gmail/Facebook style chat into your existing website.

  • Writing a JavaScript/PHP Chat Server - A tutorial

  • CometChat - CometChat runs on standard shared servers. Only PHP + mySQL required.

What is the Efficient way for build a chat script without having Server Root Access

That solution will definitely work, using setTimeout to check for new messages every second or so. There are other technologies such as comet although these are not possible in PHP, as stated in the question.

Here is an example that uses PHP and stores chat history in an SQL database, the ajax function to get new chat messages:

//Updates the chat
function updateChat(){
$.ajax({

type: "GET",
url: "update.php",
data: {
'state': state,
'file' : file
},
dataType: "json",
cache: false,
success: function(data) {

if (data.text != null) {
for (var i = 0; i < data.text.length; i++) {
$('#chat-area').append($("<p>"+ data.text[i] +"</p>"));
}

document.getElementById('chat-area').scrollTop = document.getElementById('chat-area').scrollHeight;

}

instanse = false;
state = data.state;
setTimeout('updateChat()', 1);

},
});
}

As you can see the last line uses setTimeout to call the function every 1 second.

Messages are sent separately by a different function:

//send the message
function sendChat(message, nickname) {
$.ajax({
type: "POST",
url: "process.php",
data: {
'function': 'send',
'message': message,
'nickname': nickname,
'file': file
},
dataType: "json",
success: function(data){

},
});
}

As I mentioned in my comment above, there are some advantages to using server technologies other than PHP. Most PHP solutions use a database to persist chat messages between requests to the server, this creates a lot more server overhead than is really needed, a node.js solution can instead store the messages in an array that stays in memory within the server, and in addition use sockets see here for an example.

Edit - to cache an sql query in memory

If you are using MySQL it is possible to prepend your query with a comment to imply that the query should be cached in memory, something like:

$res   = $mysqli->query("/*" . MYSQLND_QC_ENABLE_SWITCH . "*/" . "SELECT message FROM chatroom WHERE id = $roomId");

For more information see example 1, here. The example times the queries and could be useful as a benchmark on your Server.

Caching can occur on the server even if the code does not explicitly ask for it, after all the amount of memory required to store the contents of a chatroom is very small - it's only a few lines of text! The example I took the Javascript code from, above, stores the text in a file, which would almost certainly be stored in memory on the Web Server. If the db server is running on the same host as the web site then the request may well not result in any disk activity, making the overhead also very small. The webserver / db connection may also be in memory rather than sockets.

The best solution will very much depend on your server setup, best to get something working, then optimise it. I do know from experience that the node.js solution is very fast.

Edit - Answer to flag question

Setting a flag in the client Javascript would not work, as other clients could submit messages without the flag for the current client being reset. Setting a flag in the PHP on the server is tricky, persistent variables that are not specific to clients (from sessions or cookies), and not stored in databases have to be saved in files: PHP: persistent variable value. The static keyword is not quite the same as it would be in C or similar language. This is one of the main advantage of using Node.js, persistent arrays are very easy to create.

One solution would be to save the messages as json in a file, append each new message as it is received, and return the whole file to the user, once a second. The file could be restricted to 100 lines or so, this would ensure that it stays stored in cache memory somewhere (ramdisk, OS disk cache, or at worse hardware cache on the harddisk itself).



Related Topics



Leave a reply



Submit