I recently discovered the new socket functions. So I create a server that opens a port and waits for clients. It works fine but it seems that it can handle just 1 client at one moment. For ex, if the server awaits for the client to connect to the server through telnet(for ex) and the clients input some string, the server will send back to the client that string. Well, it seems that if a client connects to the server, any other client that tries to connect it can't. He enters on a buffer and only after the first client disconnects, the second client is allowed to connect.
Any ideas on how could I do that?

HERE'S THE CODE:

<?
set_time_limit (0);

$host = "and2";
$port = 1234;

// create socket
$socket = socket_create(AF_INET, SOCK_STREAM, 0) or die("Could not create socket\n");

// bind socket to port
$result = socket_bind($socket, $host, $port) or die("Could not bind to socket\n");

// start listening for connections
$result = socket_listen($socket, 3) or die("Could not set up socket listener\n");

echo "Waiting for connections...\n";

// accept incoming connections
// spawn another socket to handle communication
$spawn = socket_accept($socket) or die("Could not accept incoming connection\n");

echo "Received connection request\n";

// write a welcome message to the client
$welcome = "Enter string\n? ";
socket_write($spawn, $welcome, strlen ($welcome)) or die("Could not send connect string\n");

// keep looping and looking for client input
do
{
// read client input
$input = socket_read($spawn, 1024, 1) or die("Could not read input\n");

if (trim($input) != "")
{
	echo "Received input: $input\n";

	// if client requests session end
	if (trim($input) == "END")
	{
		// close the child socket and break out of loop
		socket_close($spawn);
		break;
	}
	else
	{
		// reverse client input and send back
		$output = strrev($input) . "\n";
		socket_write($spawn, $output . "? ", strlen (($output)+2)) or die("Could not write output\n");
		echo "Sent output: " . trim($output) . "\n";
	}
}

} while (true);

// close primary socket
socket_close($socket);
echo "Socket terminated\n";

?>

    there are two ways that this is accomplished:

    1) by forking the process

    2) with a loop

    both of these can be accomplished in PHP

    1) see the pcntl_ functions, they try to emulate the unix style process handling:
    http://www.php.net/manual/en/ref.pcntl.php

    your program starts up and waits for a connection, when it gets the initial connection to the socket, it pcntl_fork()s, which breaks itself off from your program, so now there are two identical programs, each starting onthe line of your program that the fork() happened. the original gets one signal, and the forked on get different signal, so each one knows which it is.

    You tell your original to move on and wait for the next attempt at the socket. The fork()ed process handles the request, returns the data then dies.

    this is the way apache handles things, by the way.

    2) socket_select:
    http://www.php.net/manual/en/function.socket-select.php

    you keep an array of sockets, one for each connection to your program

    socket_select will tell you which of these sockets currently has info waiting to be handled in them. you simply loop through these sockets and handler each one as it's own connection to your program.

    this function actually modifies the list you give it. so upon callling this function you need to step through the array you passed it, and see which items select() maket as changed

    when a new connection appears, you add it to the list of sockets to watch, when you recieve a done status, you remove it from the list

    this usually exists in an infinite loop, and bugs out when it recieves a special 'turn off messsage'.

      Thanks a lot for your explications, ednark. I will try that, especially the second method, because using forking requires an additional module installed. But, could u gave me a working example on that? Of course if it's not too much too to ask...
      Thanks again

        php just emulates the bsd style sockets. fortunately for you if you have your hands on a linux box with man pages, you can just

        man select

        or if you have this nefty new thing i like to call the 'inter-net' you can go to some place like:
        http://www.rt.com/man/

        these will show you how the C version of this function works, which is what php is based off of, there is even a little example there, though not very descriptive.

        if you had read the whole page at php.net:
        http://www.php.net/manual/en/function.socket-select.php

        you would have seen a link to some other guys code:
        http://dave.dapond.com/socketselect.php.txt

        this guy wrote your example you were looking for. This should start you off.

        (for others reading this thread) keep in mind that a socket based server is NOT meant to be run behind apache. It is meant to be run standalone as a daemon.

        happy coding

          Yep, I just read the comments on the php manual and i saw that script. And it's working. Nice !!!

            2 months later

            Hello, how did you manage to do it.???

            ++ckill3r

              CyberKill3r

              did you change the first line to point to the php executable on your own system?

              what part of the example code doesn't work for you. set error reporting back to on to see whats going on

                My problem is howto use multiple clients at the same time. My code is almost the same.

                ++ckill3r

                  ok well thats everybodies problem ... thats why you want this code in the first place

                  what is the first part of that code that does not work.. what error is thrown...

                  here is a rewrite of the dave.dapond.com code.. this works on my windows machine... its only the server.. you have to telnet into that port and type.. and when you are done you need to either exit with ctrl-c from the commandline where you starte the server.. or just exit from the client...

                  this code i know works with 4.2.3 on win XP

                  #!c:\php4\php.exe -q
                  <?php
                  /* a simple 'partyline' example of how to use socket_select() and co. to create
                   * a multi-socket php listening daemon with multi-client support.
                   *
                   * modify at will. (by dave on irc.dal.net)
                   */
                  
                  /* this is to be a daemon, don't want to auto-timeout and no error messages! */
                  set_time_limit(0);
                  //error_reporting(0);
                  
                  echo "[server start] program started\n";flush();
                  
                  $listening_address = '127.0.0.1';
                  $listening_port = 10000;
                  
                  define('STATE_NICKNAME', 1);
                  define('STATE_CHATTING', 2);
                  
                  echo "[server start] configuration set\n";flush();
                  
                  /* display error message */
                  function throwError() {
                    echo '[error]: ' . socket_strerror(socket_last_error()) . "\n";flush();
                  }
                  
                  /* display error message and die */
                  function throwFatal() {
                    throwError();
                    die();
                  }
                  
                  /* send a message to a socket */
                  function writeSocket ( $socket, $message )
                  {
                      socket_write($socket, "$message\n");
                  }
                  
                  /* send a message to all client sockets */
                  function writeAll ( $message, $state = STATE_CHATTING )
                  {
                      global $clients;
                      global $data;
                  
                  foreach ( $clients as $client ) {
                      if ( $data[$client]['state'] == $state ||
                           $state == 0
                         )
                      {
                          writeSocket( $client, $message );
                      }
                  }
                  }
                  
                  /* send a message to all client sockets except restricted ones */
                  function writeRestricted( $restricted_sockets, $message, $state = STATE_CHATTING )
                  {
                      global $clients;
                      global $data;
                  
                  if ( !is_array($restricted_sockets) ) {
                      $restricted_sockets = array( $restricted_sockets );
                  }
                  
                  foreach ( $clients as $client ) {
                      if ( !in_array($client,$restricted_sockets) &&
                           ( $data[$client]['state'] == $state ||
                             $state == 0
                         ) )
                      {
                          writeSocket($client, $message);
                      }
                  }
                  }
                  
                  /* handle incoming data */
                  function handleIncomingData( $socket, $incoming_data )
                  {
                      global $data;
                  
                  $data[$socket]['buffer'] = $incoming_data . $data[$socket]['buffer'];
                  /*
                      if ( $data[$socket]['buffer'] != '' ) {
                          $incoming_data = $data[$socket]['buffer'] . $incoming_data;
                      }
                  
                  $incoming_data = str_replace("\r", '', $incoming_data);
                  
                  $data[$socket]['buffer'] = substr(strrchr($incoming_data, "\n"), 1);
                  $incoming_data = substr($incoming_data, 0, strpos($incoming_data, "\n"));
                  */
                  
                  if ( $data[$socket]['state'] == STATE_NICKNAME ) {
                      actionNickname($socket, $incoming_data);
                  } else {
                      actionChat($socket, $incoming_data);
                  }
                  }
                  
                  /* handle nickname entry */
                  function actionNickname($socket, $nickname)
                  {
                      global $clients;
                      global $data;
                  
                  $nickname = trim($nickname);
                  /*
                      if ( !preg_match('/^\w{1,10}$/', $nickname)) {
                          writeSocket($socket, "That nick is invalid [{$nickname}]. Please choose another:");
                          return;
                      }
                  */
                  
                  foreach ( $clients as $client ) {
                      if ( $data[$client]['nickname']==$nickname ) {
                          writeSocket($socket, "That nick is already in use. Please choose another:");
                          return;
                      }
                  }
                  
                  $data[$socket]['nickname'] = $nickname;
                  $data[$socket]['state']    = STATE_CHATTING;
                  actionJoin($socket);
                  }
                  
                  /* handle chat text */
                  function actionChat ( $socket, $incoming_data )
                  {
                      global $data;
                  
                  /* do who command */
                  if ( $incoming_data == '.who') {
                      actionWho($socket);
                      return;
                  
                  /* check for error */
                  } else if ($incoming_data{0} == '.') {
                      sendSocket($socket, "Invalid command.");
                      return;
                  }
                  
                  $nick = $data[$socket]['nickname'];
                  //    writeRestricted( array($socket), "<{$data[$socket]['nickname']}> $data");
                      writeAll( "<{$data[$socket]['nickname']}> $incoming_data");
                  }
                  
                  /* client has joined */
                  function actionJoin( $socket )
                  {
                      global $data;
                      writeAll("*** :) {$data[$socket]['nickname']} has joined the chat ({$data[$socket]['ip_address']})");
                  }
                  
                  /* client has quit */
                  function actionQuit ( $socket )
                  {
                      global $data;
                      writeAll("*** :( {$data[$socket]['nickname']} has left the chat ({$data[$socket]['ip_address']})");
                  }
                  
                  /* who command */
                  function actionWho ( $socket )
                  {
                      global $clients;
                      global $data;
                  
                  foreach ( $clients as $client ) {
                      if ( $data[$client]['state'] == STATE_NICKNAME )
                          writeSocket( $socket, "[nickname] ({$data[$client]['ip_address']})" );
                  
                      else if ( $data[$client]['state'] == STATE_CHATTING )
                          writeSocket( $socket, "[chatting] {$data[$client]['nickname']} ({$data[$client]['ip_address']})" );
                  
                      else 
                          writeSocket( $socket, "[ghost] {$data[$client]['nickname']} ({$data[$client]['ip_address']})" );
                  }
                  }      
                  
                  echo "[server start] functions defined\n";flush();
                  
                  
                  /* assign listening socket */
                  $listening_socket = socket_create(AF_INET, SOCK_STREAM, 0)
                    or throwFatal();
                  echo "[server start] listening socket initialized\n";flush();
                  
                  /* reuse listening socket address */
                  socket_setopt($listening_socket, SOL_SOCKET, SO_REUSEADDR, 1)
                    or throwFatal();
                  echo "[server start] listening socket set to resuse address\n";flush();
                  
                  /* set socket to non-blocking */
                  socket_set_nonblock($listening_socket)
                    or throwFatal();
                  echo "[server start] listening socket set to non-blocking mode\n";flush();
                  
                  /* bind listening socket to specific address/port */
                  socket_bind($listening_socket, $listening_address, $listening_port)
                    or throwFatal();
                  echo "[server start] listening socket bound to {$listening_address}:{$listening_port} \n";flush();
                  
                  /* listen on listening socket */
                  socket_listen($listening_socket)
                    or throwFatal();
                  echo "[server start] listening socket told to listen \n";flush();
                  
                  /* set initial vars and loop until $abort is set to true */
                  $clients = array();
                  $data    = array();
                  $abort   = FALSE;
                  
                  
                  echo "[server start] starting loop of death \n";flush();
                  
                  while ( !$abort ) {
                      /* sockets we want to pay attention to */
                      $set = array_merge($listening_socket, $clients );
                  
                  $empty_write = array();
                  $empty_read  = array();
                  if (socket_select($set, $empty_write, $empty_read, 1, 0) > 0) {
                  echo "[socket noise]\n";flush();
                          /* loop through sockets */
                          foreach ( $set as $socket ) {
                              /* listening socket has a connection, deal with it */
                              if ( $socket == $listening_socket ) {
                  echo "  [new client] noise on $listening_socket \n";flush();
                                  /* get the connection to the new client */
                                  $new_client_socket = socket_accept($listening_socket)
                                      or fatalError();
                  echo "  [new client] accepted new client socket ($new_client_socket)\n";flush();
                                  /* add socket to client list and announce connection */
                                  socket_getpeername($new_client_socket, $ip_address);
                                  $clients[$new_client_socket] = $new_client_socket;
                                  $data[$new_client_socket] = array(
                                      'ip_address' => $ip_address,
                                      'buffer'     => '',
                                      'state'      => STATE_NICKNAME
                                  );
                  
                              echo "   [new client] connected from $ip_address\n";flush();
                              writeSocket($new_client_socket, 'Enter a nickname:');
                  
                          /* a client socket has incoming data */
                          } else {
                  echo "  [client] noise on $socket \n";flush();
                                  /* no error, but connection was closed, so tell everyone */
                                  if ( ($incoming_data = socket_read($socket, 1024) ) === FALSE || $incoming_data == '' ) {
                                      actionQuit($socket);
                                      // remove client from arrays
                                      unset($clients[$socket]);
                                      unset($data[$socket]);
                  echo "[client data] closing socket $incoming_data \n";exit;
                  
                              /* must be some good data from client */
                              } else {
                  echo "[client data] $incoming_data \n";
                                      /* only want data with a newline */
                                      if (strchr($incoming_data, "\n") === false) {
                                          $data[$socket]['buffer'] .= $incoming_data;
                                      } else {
                                          handleIncomingData($socket, $incoming_data);
                                      }
                                  } /// handle data
                  
                          } /// handle listening:client socket
                  
                      } // foreach client
                  
                  } // if noise on sockets
                  
                  } // while not aborting
                  
                  echo "[end]\n";flush();
                  exit;
                  ?>
                  

                    thanks dude, but the thing is that, that is to much code, please can you rewrite the first script of ednark so that can handle multiple socket, the thing is that his is almost like mine.

                    ++thanks in advanced
                    ++ckill3r

                      i dont think i can get any more simple...

                      raducosma code was very small, but did not handle multiple sockets

                      to make multiple sockets work you need more code... and to do anything useful with the many sockets you need lots more code...

                      dave's code that i rewrote is pretty much as vanilla as you can get for a chat server... which is the most vanilla thing you can do with sockets... if you were in a networking class that would be project 0, before you got the hard stuff...

                      anything usefull you write will most likely be much longer than this example... if you want... just look after the function declarations for the meat of the code... thats what you need to understand... the functions are pretty well named and should be self explanitory if you know what 'global' means

                      if anyone else reading this post has a better more simple example, please post them im sure it would help

                      if you want a simpler step by step example of how and why you do things to use socket_select you might just request an article about it... or better yet go online and look up how sockets are handled in C, google should know about tons of already written tutorials... they should be analogous to php as the functions differ little from the base c api

                      im afraid you will have to piece your way through that example... if you have any specific questions about parts of it feel free to ask... im glad to help you muck through it

                        oka thanks dude, but i was looking for an easier way to do it.

                        ++kill3r

                          Write a Reply...