PHP Socket server and Chat Gateway for Flash clients

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 😉

Download the fully

commented source code
here!

“PHP Socket server and Chat Gateway for Flash clients” bejegyzéshez 63 hozzászólás

  1. Wow that was strange. I just wrote an really long comment but after I
    clicked submit my comment didn’t appear. Grrrr…
    well I’m not writing all that over again. Anyhow, just wanted to say fantastic blog!

  2. We stumbled over here different page and thought I might
    as well check things out. I like what I see so now i’m following you.
    Look forward to finding out about your web page for a second time.
    cheap flights 3gqLYTc

  3. Hello there! Do you use Twitter? I’d like to follow you
    if that would be okay. I’m absolutely enjoying your blog and look forward to new updates.
    yynxznuh cheap flights

  4. Hi, I do think this is an excellent blog.
    I stumbledupon it 😉 I am going to revisit yet again since i have saved as a favorite it.

    Money and freedom is the greatest way to change,
    may you be rich and continue to help other people.
    y2yxvvfw cheap flights

  5. What’s Going down i’m new to this, I stumbled upon this I’ve discovered It positively useful and it has
    helped me out loads. I’m hoping to contribute & help different users like its aided me.
    Good job. cheap flights 32hvAj4

  6. Кафе русской кухни в Дубае sorpcafe.ae

    Один из самых лучших городов Мира-Дубай может удивить не только самым большим поющим фонтаном, самыми лучшими отелями и роскошными автомобилями, но и шикарной разновидной кухней. Кафе, рестораны, бары в этом районе существуют на каждый вкус.

    Хотим Вас оповестить об одном кафе Дубая русской кухни Sorp Business Cafe. Если Вы планировали найти доставка еды то для Вас данная информация будет кстати. Русская кухня весьма различна и вкусна, наиболее для тех, кто давненько не был на Родине и соскучился по наваристому борщу.

    Заходите на портал sorpcafe.ae и оцените наше меню. В ресторане Вы можете не только вкусно поесть, но также провести встречи разного характера-собрания, рабочие встречи, романтические свидания или просто позавтракать.

    У нас есть как главный зал, так и отдельные комнаты, предназначенные как раз для подобных процессов, а также по запросу мы быстро организуем всё по Вашим желаниям. Провести любой праздник, тематическую вечеринку, встречу с близкими просто вместе с нами!

    Существующее меню привлекательно и велико, мы постоянно пополняем позиции. Вы можете узнать фото, состав блюд и стоимость на нашем сайте прямо сегодня.

    На завтрак мы рады предложить блины с лососем, с творогом, с клубничным джемом, круассан с шоколадом, сэндвич с лососем, омлет, яйца бенедикт с копченым беконом, мюсли и многое другое. Супы и салаты: русский борщ, окрошка, солянка, уха, цезарь с креветками, греческий салат, оливье, салат из осьминога и другие.

    Известный русские пельмени и вареники с различными начинками. Вторые блюда также представлены в большом ассортименте: котлета по-киевски, рыбная котлета, говяжьи ребрышки, котлеты из говядины с курицей, гуляш из говядины, осьминог на гриле, голубцы. Также Вы можете заказать чебуреки, пирожки, лагман или пасту, различные гарниры. Напитки, которые есть в нашем ресторане: ягодные смузи, всевозможные кофе и чаи, соки, компот и кисель.

    Чтобы оформить заказ еды с доставкой, зайдите и узнайте все подробности на страницу – ресторан в дубае прямо сейчас. Расположены по адресу: UAE, DUBAI, JLT, CLUSTER F. Телефон +971 4 589 8003. Либо Вы можете написать в форму доставки на нашем интернет портале, указав ваши личные данные, место положения и позиции необходимых блюд.

    Мы доставим Вам Ваш заказ в самые быстрые сроки, в то время как Вы можете заняться своими любимыми делами, провести время с близкими или друзьями, поработать или просто погулять, не думая про готовку. Или закажите столик в нашем кафе по написанному телефону или на данном сайте sorpcafe.ae можно нам написать электронное письмо со всеми пожеланиями.

  7. Если вы хотите ехать в горы или в другой город зимой, стоит одевать угги. Удобные UGG вы найдёте на официальном веб-ресурсе интернет-магазина UGG. Мужские и женские угги очень привлекают молодую публику.

    На australia-msk.ru можно найти и подобрать угги австралия и унты. Много моделей UGG стали в тренде благодаря натуральным материалам и превосходному пошиву. Подошва со специальной анти-скользящей защитой, которая есть во многих парах, сможет защитить человека от травм при падении. Такая обувь очень гладкая снаружи и привлекательная на вид.

    Она отлично подойдёт под ваш рабочий костюм или штаны и поло. В холодные месяцы года угги выглядят стильно с любой одеждой. Основное преимущество такой зимней обуви – она нереально теплая. В целом, угги хорошо сочетаются с коричневыми и бордовыми джинсами и кофтами. Большинство девушек предпочитают в зимние месяцы также носить UGG со спортивными брюками, если нужно выйти в сквер или недалеко от дома. К тому же, серые и коричневые мини-угги будут симпатично смотреться с юбками короткого кроя.

    В интернет-магазине невероятно много моделей, которые сразу Вам понравятся. UGG Australia кожаные ботинки и детские угги также доступны на портале. Если вы стремитесь сделать подарок или сделать презент своей девушке или жене на годовщину, UGG из прочных материалов с овчиной – великолепный вариант!

    Наиболее популярные и гладкие модели UGG распродаются в компании очень быстро. Среди самых популярных моделей надо отметить: Mini Bailey Button Bling Metallic Black; Classic Mini II Sand; Men’s Classic Mini II Chocolate

    В инет-магазине также в целом в тренде официальный интернет магазин угги австралия и UGG в натуральном бежевом цвете. К тому же, коллекция LUX стала хитом этого года. На ресурсе можно найти поиск товаров по каталогу, в списке очень много добавлено новых позиций. Женские ботинки UGG считаются самыми популярными и удобными среди других женских брендов. Также, UGG Women’s Cheyeenne Dusk или Women’s Sioux Chesnut пользуются огромной популярностью у студенток.

    Угги – это не только зимняя обувь, есть новые модели уггов для весны и осени. Достаточно много моделек из новинок UGG также представлены в каталоге. Доставка возможна по указанному вами адресу, производится доставка, на практике, на 2-ой день после заказа. Вы также можете оплатить товар картой Visa или MasterCard на сайте заранее.

    При необходимости, вы можете оплатить за товар наличкой курьеру. Забрать угги, забронированные заранее вы можете в г. Москва, Проспект мира, д. 102, к.1, или узнать подробности по любой интересующей Вас продукции по контактам +7(495)7489547, где вас детально проконсультируют.

  8. Универсальные кроссы New Balance будут отлично смотреться на Вас в летнее время года. Большинство единиц NB созданы так, что нога в них чувствует себя без проблем. Кроссовки великолепно подойдут для летних месяцев, чтобы гулять в парках или скверах. Великолепно будут себя чувствовать, как мужчины, так и женщины в кроссах NB зимой. Много известных кроссовок NB оснащены технологией, которая согревает ногу в зимнее время года.

    На newbalance-russia.com вы найдёте много кросс NB. Если вы стремитесь одевать оригинальные кроссовки New Balance то переходите на ссылке – купить кроссовки new balance у нас вы сможете подобрать себе лучший вариант. На веб-странице интернет-магазина находится более 400 единиц кроссовок. Вы можете открыть ассортимент мужских или женских моделек и заказать подходящую модель.

    Кроссовки New Balance в целом комфортные, их удобно одевать в любое время года. На веб-странице вы можете выбрать интересующую Вас модель и нажать «В корзину». Кроссовки сразу попадут в ящик, и вы сможете их оплатить. Также, кроссы можно оплатить с помощью кредиток Виза или MasterCard на ресурсе, или при получении у курьера. В ассортименте доступны стильные модели, поэтому выбрать подходящий вариант не составит труда.

    На официальном сайте New Balance доступны не только самые известные товары, но и классические модели для спорта от производителя. Компания разрабатывает ежесезонно много вариантов кроссовок как для мужчин, так и для женщин. Каждый человек сможет подобрать и приобрести идеальный вариант. В каталоге есть позиции белого, бордового и зелёного цветов.

    Существенный интерес у дам вызывают кроссы NB желтого и розового цвета. Среди самых известных кроссовок и новинок NB нужно отметить: кроссовки New Balance 574; кроссовки NB 420; модель кроссовок New Balance 999. Великолепно будут смотреться как на парне, так и женщине любые кроссовки из зимней коллекции.

    На ресурсе – мужские кроссовки нью баланс купить в москве вы подберете разные товары, в том числе отображаются и кроссовки со скидкой. Огромный каталог кроссовок New Balance, который представлен на веб-странице, позволяет выбрать кроссы для весны. Если у клиента появляются вопросы, задать их есть возможность по телефону +7(495)532-56-69, или можно приехать сразу по адресу г. Москва, ул. Хабаровская, дом 15, где и находится интернет-магазин.

    На портале есть стильные модельки, которые оснащены специальной стелькой и технологией, которая распределяет нагрузку стопы. Это позволяет чувствовать существенный комфорт. Человек может наслаждаться даже в зимней спортивной экипировке кроссами NB, так как они являются комфортными и популярными.

Vélemény, hozzászólás?

Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük