First of all, I don’t plan to write a step-by-step manual, „PHP Socket server and Chat Gateway for Flash clients in 10 minutes” or something like that.
We’ve just finished the developement of a Flash chat solution, that uses PHP as Backend and Chat Gateway. I’ll present pieces of the code, and some tricky solutions, for example how to communicate with flash on 80 port.
I won’t present the Flash part of the story, because it was done by a collegue and a friend of mine, and I won’t release his code.
For a starting point, you can check out kirupa.com – PHP 5 Sockets with Flash 8 tutorial, as we did.
So in this article I will show our solution of a chat server in php 5, for flash clients, and a webserver emulation for policy-file-request and crossdomain.xml request, and xml-socket based communication. This example only shows how to create a multiuser chat, with only one-to-one communcation, so you can have several conversations, but no „chat rooms”.
This will be a command line (CLI) PHP of course, so we won’t need a webserver for that.
So, we need to create a daemonized php, without execution time limit. So it could loop until the end of time, or the next reboot 🙂 And also we set the ip address, and the port to listen on.
#!/usr/bin/php -q <?php set_time_limit(0); ob_implicit_flush(); $address = '127.0.0.1'; $port = 80; |
Let’s create an array for the incoming connections data, (if you use it for chat, it would be useful to store nicknames in it.), and start creating and listening a socket. Please note, that everything I „echo” goes to a log file in our case.
$_sockets = array(); if (($master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0) { echo "socket_create() failed, reason: " . socket_strerror($master) . "\n"; } socket_set_option($master, SOL_SOCKET,SO_REUSEADDR, 1); if (($ret = socket_bind($master, $address, $port)) < 0) { echo "socket_bind() failed, reason: " . socket_strerror($ret) . "\n"; } if (($ret = socket_listen($master, 5)) < 0) { echo "socket_listen() failed, reason: " . socket_strerror($ret) . "\n"; } else { $started=time(); echo "[".date('Y-m-d H:i:s')."] SERVER CREATED ( MAXCONN:".SOMAXCONN." ) \n"; echo "[".date('Y-m-d H:i:s')."] Listening on ".$address.":".$port."\n"; } $read_sockets = array($master); |
SOMAXCONN is a kernel variable that sets how many socket connections can your box handle. In unix world it can be set at kernel compile time, or it can be adjusted throught sysctl.
After this we’ll create a persistent loop to handle requests.
while (true) { $changed_sockets = $read_sockets; $num_changed_sockets = socket_select($changed_sockets, $write = NULL, $except = NULL, NULL); foreach($changed_sockets as $socket) { if ($socket == $master) { if (($client = socket_accept($master)) < 0) { echo "socket_accept() failed: reason: " . socket_strerror($msgsock) . "\n"; continue; } else { array_push($read_sockets, $client); echo "[".date('Y-m-d H:i:s')."] CONNECTED "."(".count($read_sockets)."/".SOMAXCONN.")\n"; } } else { $bytes = @socket_recv($socket, $buffer, 2048, 0); /* Here comes the core... ;) */ } } |
So, these are the basics. Until this point, this code is almost the same as Raymond Fain’s socketShell.php mentioned above.
As I commented we’ll only edit the else part of the code.
So every code I quote now, will go inside this else part. Until we start defining functions. These functions will go outside, the loop.
First we’ll do the webserver emulation part. Why do we need this?
Because my plan is to communicate on port 80, because firewalls and routers won’t block communication on this. But the problem is that – as you might know – Flash can’t communicate under port 1024 by default, because of security issue.
But there is a way, to get flash to communicate on any port you want. This is called cross-domain-policy.
Crossdomain policy is a data, in XML format, that looks like this, in our case:
So I store it in a variable, and if my socket daemon gets a request from flash that looks like this:
<?xml version="1.0"?> <cross-domain-policy> <allow-access-from domain="*" to-ports="80" /> </cross-domain-policy> |
In the answer we’ll give back the crossdomain policy file, that means we allow connection to port 80.
If you want to know more about crossdomain policy click here.
So our php code after the socket_recv will look like this:
if (preg_match("/policy-file-request/i", $buffer) || preg_match("/crossdomain/i", $buffer)) { echo "[".date('Y-m-d H:i:s')."] CROSSDOMAIN.XML REQUEST\n"; $contents='<?xml version="1.0"?><cross-domain-policy><allow-access-from domain="*" to-ports="80" /></cross-domain-policy>'; socket_write($socket,$contents); $contents=""; $index = array_search($socket, $read_sockets); unset($read_sockets[$index]); socket_shutdown($socket, 2); socket_close($socket); } |
We close this socket, because flash request for that policy file. It closes the connection, and if it get’s a proper answer, it will reconnect, and that connection will be a true socket now.
After this flash thing, we’ll created a nice feature in our server. A server-status information. Which can be reached throught a web browser, by typing the IP address (and port if you don’t use 80) that the daemon is listening on, and /server-status.
Like http://127.0.0.1/server-status. And it will answer something like this:
OK Clients: 48/128 Created: 2007-07-10 10:54:02 Uptime: 16 days |
After this. I ignore favicon.ico requests, because if you check-out your server-status information from a browser it will automatically request it, and it will just create a piece of junk in the code.
And the last thing, I webserver emulation part, that we redirect every request coming to our server, that is a GET, POST or HTTP head request and not socket communication.
And we also log that request, and create a real HTTP redirect.
So let’s see what we should add to the exsiting code, to work that way.
elseif (( preg_match("/GET/", $buffer) || preg_match("/POST/", $buffer)) && preg_match("/HTTP/", $buffer)) { if (preg_match("//server-status/i", $buffer)) { $uptime = floor((time()-$started)/86400); socket_write($socket,"OK\n"); socket_write($socket,"Clients: ".count($read_sockets)."/".SOMAXCONN."\n"); socket_write($socket,"Created: ".date('Y-m-d H:i:s',$started)."\n"); socket_write($socket,"Uptime: ".$uptime." days\n"); echo "[".date('Y-m-d H:i:s')."] STATUS REQUEST\n"; } elseif (preg_match("/favicon.ico/i", $buffer)) { //ignore :) } else { // fake web server socket_write($socket,"HTTP/1.1 301 Moved Permanently\n"); socket_write($socket,"Server: PHP Chat Server by DjZoNe - http://djz.hu/\n"); socket_write($socket,"Date: ".date("d, j M Y G:i:s T\n")); socket_write($socket,"Last-Modified: ".date("d, j M Y G:i:s T\n")); socket_write($socket,"Location: http://djz.hu/\n"); } $index = array_search($socket, $read_sockets); unset($read_sockets[$index]); @socket_shutdown($socket, 2); @socket_close($socket); } |
Until now we did, the socket server starting, and the http request handling.
Now we do the last part, the quit part. I show what we do when the socket pipe is broken, the user left or something like that happens.
if (strlen($buffer) == 0) { //we get the user's uniqe id from the database $id=$_sockets[intval($socket)]['nick']; $index = array_search($socket, $read_sockets); unset($read_sockets[$index]); // we clean up unset($_sockets[intval($socket)]); // we clean up our own data // cleaning up is essential when creating a daemon // we can't leave junk in the memory @socket_shutdown($socket, 2); @socket_close($socket); $allclients = $read_sockets; // reload active clients // $socket is now pointing to a dead resource id // but the send_Message() function will need it, I'll explain later send_Message($allclients, "<quit aid=\"".$aid."\" />"); echo "[".date('Y-m-d H:i:s')."] QUIT ".$id."\n"; } |
And now the real socket communication…
else { $allclients = $read_sockets; array_shift($allclients); $piece = explode(" ",trim($buffer)); // we strip out all unwanted data $cmd = strtoupper($piece[0]); } |
We assume, that the a command looks like in IRC protocol.
MSG who message
We cut the content to pieces, and we glue again together when it is a message, otherwise we only need the first few arguments.
if (!empty($piece[1])) $content = $piece[1]; switch ($cmd) { case "IDENTIFY": $id = trim($piece[1]); $passwd = trim($piece[2]); send_Identify($allclients, $socket, $id, $passwd); break; case "MSG": $id = trim($piece[1]); $msg=""; foreach ($piece as $key=>$val) { if ($key > "1") $msg.=$val." "; } $msg = trim($msg); send_Msg($allclients, $socket, $id, $msg); break; case "LIST": list_Users($allclients, $socket); break; } |
We made the command triggering.
Until now we were in the loop.
From this point, we’ll create functions, outside.
I want to tell a story… Just joking 😉
But, we found out something. We did the socket_write, and everything, but in won’t get through the socket until we put an ASCII 0 (zero) at the end of the output buffer.
So we insert an ascii-0 code after every socket_write. I guess this is just beacause of flash xml-socket communication. If you look back to the „web server emulation” part, we just used newline.
So here are some functions, we use for authentication, message sendig and so on.
function send_Identify($allclients, $socket, $id, $passwd) { global $_sockets; $nicks = array(); $dbconf = new DATABASE_CONFIG; $db_host = $dbconf->host; $db_base = $dbconf->database; $db_login = $dbconf->login; $db_password = $dbconf->password; foreach ($_sockets as $_socket) { foreach ($_socket as $key=>$val) { if (empty($nicks[$val])) $nicks[$val]=1; else $nicks[$val]=$nicks[$val]+1; } } if (empty($nicks[$id])) { $s=1; // Here will be a simple authentication. $link = mysql_connect($db_host, $db_login, $db_password); if (!$link) die("Could not connect:" . mysql_error() . "\n"); $db_selected = mysql_select_db($db_base, $link); if (!$db_selected) die("Can't use $db_base :" . mysql_error() . "\n"); $result = mysql_query("SELECT nick FROM members WHERE id='".intval($id)."' AND password='".crypt($passwd)."' AND active='1' LIMIT 1"); $data = mysql_fetch_array($result); $name = $data['name']; $_sockets[intval($socket)]=array('id'=>$id, 'nick'=>$name); mysql_free_result($result); mysql_close($link); |
It’s essential to use free and close on the sql connection.
Or I have to say you MUST close, otherwise the connection will timeout, and at the next connect the whole daemon will die.
} else $s=0; // We'll answer to the flash in XML form. // But we receive in plain text format. if ($s == 1) { $out = "<identify aid=\"".$nick."\" name=\"".$name."\" />"; send_Message($allclients, "<login aid=\"".$nick."\" name=\"".$name."\" />"); // this goes to all active, identified clients echo "[".date('Y-m-d H:i:s')."] LOGIN ".$id."(".count($allclients)."/".SOMAXCONN.")\n"; } else $out = ""; socket_write($socket, $out.chr(0)); // write back to the client } function send_Msg($allclients,$socket,$id,$msg) { global $_sockets; if (!empty($_sockets[intval($socket)])) { $nicks = array(); //amig fut a parancs ebben vannak a nickek. foreach ($_sockets as $_socket) { foreach ($_socket as $key=>$val) { // this check's the onliners if (empty($nicks[$val])) $nicks[$val]=1; else $nicks[$val]=$nicks[$val]+1; // we shouldn't have duplicated nicks, but what if... } } foreach($allclients as $client) { if (!empty($_sockets[$client]['nick']) && ($_sockets[$client]['nick'] == $id)) { $_client = $client; $out = "<msg aid=\"".$_sockets[$socket]['nick']."\" time=\"".date("H:i:s")."\" msg=\"".$msg."\" from=\"".$_sockets[$client]['nick']."\" />"; } elseif(empty($nicks[$id])) //not online or something similar { //backto the sender $_client = $socket; $out = "<error value=\"User is already left.\"/>"; } } } else { //backto the sender $_client = $socket; $out = "<error value=\"Not identified.\"/>"; } if (!empty($out)) { socket_write($socket, $out.chr(0)); //send to back ourself. we have to handle it in flash socket_write($_client, $out.chr(0)); //send to the recipient } } |
And now, we create the function, which sends message to all connected clients. And the last one shows, how to list identified users.
function send_Message($allclients, $socket, $buf) { global $_sockets; foreach($allclients as $client) { @socket_write($client, $buf.chr(0)); } } function list_Users($allclients,$socket) { global $_sockets; $out = "<nicklist>"; foreach($allclients as $client) { if (!empty($_sockets[$client]['nick']) && ($_sockets[$client]['nick'] != "")) { $out .= "<nick aid=\"".$_sockets[$client]['nick']."\" name=\"".$_sockets[$client]['name']."\" />"; } } $out .= "</nicklist>"; socket_write($socket, $out.chr(0)); } ?> |
So our daemon now handles three basic commands, which is identification, user list, and message sendling.
That’s what I promised at the beginning. You can upgrade that much more. For example, we don’t have nick changing, we don’t have the action command which is known as „/me”… Etc.
So that’s all. The whole source can be downloaded here.
And I’ve got a present for you, for the end.
Here is a tiny BASH start script, or init script for the daemon.
#!/bin/sh if [ "X$1" = "Xstart" ] ; then chmod +x /var/www/chat/phpircgateway.php /var/www/chat/phpircgateway.php >> /var/log/chat/chat.log & echo "Starting chat" fi |
Happy coding 😉
hi, i´m trying to build a multiuser game with this code,
but i need to implement „private message” in this chat code, so i can comunicate the only two players and not every people connected.
how can i do that?
very thank´s!
romanov
I’ll send you the whole code tonight 🙂
thank´s! it will be highly appreciated!!
[]
romanov
[quote]
We did the socket_write, and everything, but in won’t get through the socket until we put an ASCII 0 (zero) at the end of the output buffer.
[/quote]
This is actually mentioned in the Flash manual for their XMLSocket class 😉
Great article, used it to overcome a particular problem with my own version (multiple connection handling). It really is quite easy once you get to work on it for a few nights 😀
Please note, that I’m not a flash coder…
I’m a PHP Developer, and I find out that ascii 0 bug, all by myself 🙂
Well, the code I show here, is just a skelton.
The daemon I wrote has an uptime of 216 days now. So it’s running quite well 😉
Hi there,
great article!
I’m actually looking for the same thing as Romanov. Is it possible to send me the code as well?
Tnx a lot!
Jeroen
Sure I’ll send you as well 🙂
Back that up, great article
Sorry to make this comment section sound like a broken record, but i’d really appreciate the code too… I’m building a chat-room based game…
Thx for writing the article Dj…
Rich
This is a great article.
I am new to php programming. I have looked at a few start chat programs on the internet, but i haven’t seen any that talk to a database. Could you send me a copy it would be a great start for the site I am tring to do.
Thank you
Greg
Is it possible for you to send me the php code for this? I could do it myself but I’d like to have a complete source to compare it to so I know that the resource/memory management is ok.
This is the best tutorial I’ve seen for PHP/Flash sockets. Thanks for taking the time to write it – much appreciated.
I hate to beg but any chance you could send me the source-code too ?
I put a big nice button in the end of the article, so you can download a more complete, and documented source now 🙂
But, I send you Steven anyway 🙂
Hi,
thanks for sharing the code!
I wrote a little bit more extended and reusable socket server basing on your code.
please take a look at:
http://websvn.devayd.com/wsvn/ayd/cake/vendors/socket_server/#_cake_vendors_socket_server_
u: anon
p: anon
regards, danielz
Hi, I have added a short article in Spanish on how to use that socket server we wrote basing on this code.
Give it a look here: http://blog.devayd.com/?p=12
regards, danielz
This is ridiculously amazing! Great work!
This is exactly what I needed! Thanks!
I was wondering if you could give an hint on how to use the BASH script.
Cause i’ve finish my php socket server and my chat who use flash and ajax. (tks for your advice!)… but so clueless about BASH and init.d and Service and daemon.. i don’t have a clue.
tks
plehoux, just use it, like an init script. 😉
well .. here what i have done …. i just run a cron on this file every 10 minutes … so everything is always up (flash policy server and my Php chat server):
/dev/null &”);
printf („Serveur flash policy started! \n”);
}
$output = `lsof -ni tcp:1025`;
if($output) {
echo „Port 1025 in use \n”;
} else {
printf („Port 1025 not in use \n”);
printf („Starting chat… \n”);
exec („/usr/bin/php -q /home/zzz/socket/socketShell.php >/dev/null &”);
printf („Chat started! \n”);
}
?>
My next question:
I have you been able to change the the somaxconn variable? I have change it with sysctl, but my server still fail when reaching hight number of connexion… connection are but into queue…
well you should read:
$output = `lsof -ni tcp:1025`;
if($output) {
echo “Port 1025 in use \n”;
} else {
printf (”Port 1025 not in use \n”);
printf (”Starting chat… \n”);
exec (”/usr/bin/php -q /home/zzz/socket/socketShell.php >/dev/null &”);
printf (”Chat started! \n”);
}
That’s nice, but I think, it’s better to handle socket cheking inside the script, like I did.
Thanks a lot for the taking the time to explain it in detail , i haven’t tested it yet but im sure it will work.
I just have one question , i tried a few weeks ago an socket server implementation with php and an html form to communicate with the server, and after the socket server answered it closed.
Could that be to a bad configuration of the socket server or is this how it should work?
New to php, exploring sockets, thanks
Hey really great article here! Its been around 2 years I think since I’ve touched my socket script, but I’m getting back into the swing of things esp with the new AS3 and php 2.8. I wont be able to work as much as I’d like to (i’m in the army and deployed right now) so I’ll be on and off for the next year, but I hope to redo this script to make it worth while with it implemented to an actual server (lots have asked about that over the years) including a full blow flash client with multiple chat rooms and a few example multiplayer games for people to mess with. I’d like to make it all open source of course for people to build their own from and possibly learn from. Anyways good article! Thanks for acknowledgment!
Raymond Fain
Hi Raymond,
thank you for your feedback, I glad you liked my article. I’m looking forward to your upcoming solution.
I hope they treate you well in the army :))
I am trying to implement a multiuser chatroom with private message as Romanov and Jeroen.
Could you send me the code as well?
Thx a lot!
very good example! found some in the web, but this one make things work!
thkx
Hi;
your tutorial is very good but i can’t download your sample link…
can you check it or mail me your files?
i will be very happy if you help me to use your tutorial.
thanks a lot
Best Regards
R.Amya
I’ve corrected the link 🙂
Thanks for reporting the problem 🙂
thanks a loooooooooooooooooooot for your very very very fast reply
Have Fuuun
hi;
I’m try to learn how i can create flash chat with socket but until now i can’t use your example True.
I see a lot of Tutorial in web about chat with socket but i can’t use them true too… 🙁
for sample please see here
http://www.kirupa.com/developer/flash8/php5sockets_flash8.htm
i read examples and think i learn good how your code work, but i don’t know how step bye step use them. for example how i must create and use your files in „Ubuntu” (linux) with „Konsol”. i setup „Lamp Server” in my ubuntu and copy your samples in my www of localhost and change mode same your code and add to your sample a „test.swf” for chat; but my flash chat can’t connect to socket.
can you update your tutorial to learn how can i use your code?
i trust to your reply…
thank a lot for your patient to read my bad English writing.
Best Regards
And goodbye
R. Amya
My code actually DOES NOT need a webserver…
Because it is a standalone server itself, with some webserver emulation built-in.
It is PHP-CLI (command line) script. To get this work correctly, you also need to select a port to run on. The default is 80, but if you are using on a developement machine another services may using port 80 (like Apache, nginx, lighttpd)… So if you want to use it on port 80, you’ll need a separate IP.
The other thing is, that if you want the script to be able to communicate on port 80, you’ll need to run in as root user. Because other users can’t open ANY port below 1000.
Se sale!, muchas gracias 😀
(Is great!, thx very much :D)
Hasta luego
Thanks!
Exactly what I needed.