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

3 comments:

Victor said...

array_push($this->clients($this->socket));

i have a problem in that line, clients its a variable, not an object, return a problem because you are passing a parameter...

im trying to run the example to see how works...

jean caffou said...

Actually, I haven't tested this code, so I have no clue if it works or not, it was ment to be just a basic idea.
The code you're having problems with should be like this:

array_push($this->clients,$this->socket);

jean caffou said...

Check out this article for more tips and tricks

http://dev.kafol.net/2011/02/php-socketselect-socketwrite-and.html