Nov 30, 2008

Even PHP sometimes needs explicit data type conversions

If you're new to PHP, everything seems so easy... When printing, integers automaticly become strings, when parsing user input, strings are automaticly numbers... But sometimes that system fails. You always need to be sure what you're gonna get or use.

It happened to me one day, when writing my Briscola socket server - I was parsing an XML and expecting a number in a node, so I could use it to access a certain index in the array. Of course the array index was numeric, and I had to do a
$my_number += 0;
to it. Which is kinda dumb, but it worked. It fixed the error, and everything went smooth. But that's not really the way to do it,
$my_number = intval($my_number);
is.

Nov 23, 2008

Writing a chat server socket in PHP

UPDATE: Check out this article for more information

I've spent quite a few hours around this to learn a few tips and tricks:
  • Don't use PHP's socket_read() - allthough with PHP_BINARY_READ you *CAN* solve a lot of your problems, but not all of them. I noticed that eventually the client socket "jamms" because the buffer doesn't clear, so you get stuck with a socket that you can only write to, but when you try to read from this socket, nothing returns. A very good alternative is socket_recv()
  • OOP (Object Oriented Programming) is a good thing, it makes upgrades and fixes easy to do, so at least you would need two classes, one for the server and one for a user. Each user is an object.
  • When reading from a socket, concatenated commands may occur, use a special delimiter (char(0) works) to split them.
  • Use nonblocking sockets, you don't want your server to freeze everytime a client socket writes.
  • "Ping" your clients, sometimes the kill signal gets lost, the server thinks the socket is still active and unitl you write to that socket, there is no way of knowing if the connection is still established or not. Disconnect the socket on read or write error and that way your sockets will be purged automatically.
Here's a rough framework of what I'm talking about (may be updated some time soon):
class user {
 var $socket;
 function user($socket) {
  $this->socket=$socket;
 }
}

class server {
 var $socket;
 var $working;
 var $clients;
 
 //constructor
 function server($port) {
  $this->socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
  socket_set_option($this->socket,SOL_SOCKET,SO_REUSEADDR,1);
  socket_bind($this->socket,0,$port);
  socket_listen($this->socket);
  socket_set_nonblock($this->socket);
  $this->working = true; //if all else fails, this does too!
  array_push($this->clients($this->socket));
 }
 
 function me($sock) {
  //if the socket is the server socket
  return $this->socket == $sock;
 }
 
 //read from the buffer
 function read(&$sock,$i,$len=2048,$flag=0) {
 
 $recv_data = @socket_recv($sock,$jammeddata,$len,$flag);
 if ($recv_data == 0) {    
  //error on read or client quitted
  $this->remove_client($sock);
 } else {
  //reading from socket
  $jammed = explode(chr(0), $jammeddata); //is it jammed?
  if (count($jammed) > 2) {
   //socket is jammed, parse it
   foreach($jammed as $unjammed) {
    $this->on_read($unjammed,$i);
   }
   return true;
  }
  //clean read
  return $this->on_read($jammeddata,$i);
 }

 return false;
}
 
 //event handler - PUT ALL YOUR FUNCTIONS HERE
 function on_read() {
  
 }
}

$server = new $server(10000);

while($server->working) {
 
 //put all current clients in the array
 $clients = $server->clients;
 //put all sockets that are trying to wrote in the array
 socket_select($clients,$write=NULL,$except=NULL,0);
 
 //loop through the generated array of clients
 foreach($clients as $client) {
  if($server->me($client)) {
   //accept incoming connections
   array_push(new user(socket_accept($client)));
  }
  //read from the socket that is trying to write
  $server->read($client);
 }
 
}

UPDATE: Check out this article for more information