views:

992

answers:

2

I created a PHP Socket Server with PHP_NORMAL_READ mode. So, a message to the server is read when it ends with \n or \r. I tried it by connecting to the server with multiple telnet instances, and it works great.

However, when I connect to the server with 1 flash application and 1 telnet application (I first start the flash one), the flash one seems to make the server hang - the server is getting stuck somewhere and no longer receiving data from eg. the telnet client.

Because anyone can code a flash client, this has to be fixed server side. The server's code:

<?php
// config
$timelimit = 60; // amount of seconds the server should run for, 0 = run indefintely
$port = 9000; // the port to listen on
$address = $_SERVER['SERVER_ADDR']; // the server's external IP
$backlog = SOMAXCONN; // the maximum of backlog  incoming connections that will be queued for processing

// configure custom PHP settings
error_reporting(1); // report all errors
ini_set('display_errors', 1); // display all errors
set_time_limit($timelimit); // timeout after x seconds
ob_implicit_flush(); // results in a flush operation after every output call

//create master IPv4 based TCP socket
if (!($master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP))) die("Could not create master socket, error: ".socket_strerror(socket_last_error()));

// set socket options (local addresses can be reused)
if (!socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1)) die("Could not set socket options, error: ".socket_strerror(socket_last_error()));

// bind to socket server
if (!socket_bind($master, $address, $port)) die("Could not bind to socket server, error: ".socket_strerror(socket_last_error()));

// start listening
if (!socket_listen($master, $backlog)) die("Could not start listening to socket, error: ".socket_strerror(socket_last_error()));

//display startup information
echo "[".date('Y-m-d H:i:s')."] SERVER CREATED (MAXCONN: ".SOMAXCONN.").\n"; //max connections is a kernel variable and can be adjusted with sysctl
echo "[".date('Y-m-d H:i:s')."] Listening on ".$address.":".$port.".\n";
$time = time(); //set startup timestamp

// init read sockets array 
$read_sockets = array($master);

// continuously handle incoming socket messages, or close if time limit has been reached
while ((!$timelimit) or (time() - $time < $timelimit)) {
    $changed_sockets = $read_sockets;
    socket_select($changed_sockets, $write = null, $except = null, null);
    foreach($changed_sockets as $socket) {
     if ($socket == $master) {
      if (($client = socket_accept($master)) < 0) {
       continue;
      } else {
       array_push($read_sockets, $client);
      }
     } else {
      $data = @socket_read($socket, 1024, PHP_NORMAL_READ); //read a maximum of 1024 bytes until a new line has been sent
      if ($data === false) { //the client disconnected
       $index = array_search($socket, $read_sockets);
       unset($read_sockets[$index]);
       socket_close($socket);
      } elseif ($data = trim($data)) { //remove whitespace and continue only if the message is not empty
       echo "we received: ".$data."\n\n";
       //handleData($data, $socket);
      }
     }
    }
}
socket_close($master); //close the socket
echo "[".date('Y-m-d H:i:s')."] SERVER CLOSED.\n";

//function to write to the flash client
function flash_write($socket, $msg) {
    socket_write($socket, $msg.chr(0x0));
}
?>

Does anyone know what may cause this? I tried changing the timeout on the socket_select from none to 0 (instant return), but that didn't seem to change anything.

+1  A: 

One thing to help you debug: error_reporting(1) does not enable the display of all errors. Look at the documentation at http://us3.php.net/manual/en/function.error-reporting.php. You need something like error_reporting(E_ALL).

Alex
+2  A: 

Could you post the source of the flash client? That would show what the problem is?

Are you sure the last thing you send from the flash client is a \n ?

Otherwise the server would block on socket_read() as the flash client socket can be read without blocking (triggered socket_select()), but doesn't send the ending \n.

bucabay
@bucabay has the right answer. socket_select() will trigger when data is ready on the socket, but will not guarantee that the data contains the terminal newline that socket_read($socket, 1024, PHP_NORMAL_READ) is waiting for. If you want to protect against DoS by a malicious (or just negligent) client you will need to read in binary mode and look for the newline yourself.
bd808
I will try binary mode. However, I did say that removing the socket_select timeout did not help.
Tom
Second note: I think socket_select is not the problem (tested it aswell) because it is looking for input from all clients. So, if client #2 says something but client #1 does not - it will continue regardless. It will only not continue if 0 clients do something, but that is a good thing.
Tom
I'm now using $bytes = @socket_recv($socket, $buffer, 1024, 0); and the funny thing is that when I send the message with \n at the end with flash it is received as 1 whole message. Looks like I don't have to look for newlines myself then... strange though?
Tom
RE: socket_select() timeout. I think you should give socket_select() a large timeout, hopefully tell it to never time out. That way you let it block (sleep) until something happens on those sockets. If you give it a short timeout, you're just adding to the while() loop iterations and using up CPU. Using a timeout of 0 means you have to implement your own sleep() which is inefficient compared to letting socket_select() wake you up when you need to. Note: I think, I'm about 2 days old with sockets.
bucabay
I've been using the equivalent of @socket_recv($socket, $buffer, 1024, 0); also. My understanding is that you cannot guarantee you'll get the whole message though. Since socket_select() only guarantees that a socket will not block if read, not that you will read the whole message - like bender noted. In my implementation the sever receives a JSON string, so it can tell if it is a complete message when the JSON is "well formed". So it kind of acts as a delimiter.
bucabay