I am using streaming sockets in PHP to read from a remote server. When the remote server goes away after connection, stream_select continues to show a changed stream on the read portion of the stream, but the data being read in is a blank string.
Here is a small case that reproduces the bug. It is two components, a server and client component.
In order to replicate the bug you will need to do the following using php from the command line:
1. Start up server.php
2. Start up client.php
At this point the server should show ‘Press return to continue…. or CTRL-C’ and the client should show ‘Kill your server. Press return to continue….’
- Ctrl-c server.php
- Press enter on client.php
At this point you should see debugging output from client.php showing the issue (you will want to ctrl-c pretty quickly, it prints a lot of repeating information very quickly)
I am unsure why the stream_select continues to show the read stream as having changes after the server component is no longer running.
server.php
<?php
$socket = stream_socket_server("tcp://0.0.0.0:51111", $errno, $errstr);
$s = stream_socket_accept($socket);
print("Press return to continue.... or CTRL-C me");
fread(STDIN,1); // Wait for one character to be pressed.
fwrite($s, "Yep here's some stuff for you\0");
?>
client.php
<?php
$url = "localhost";
$port = 51111;
$errno = 0;
$errstr = "";
$fp = @stream_socket_client("tcp://".$url.":".$port, $errno, $errstr, 5);
if (!$fp)
{
print( "Unable to open socket: $errstr ($errno)\n" );
throw new Exception( "Unable to open socket : $errstr ($errno)" );
}
//WAIT HERE.
print("Kill your server. ");
print("Press return to continue....\n\n");
fread(STDIN,1); // Wait for one character to be pressed.
stream_set_blocking($fp,0);
$buffer = "";
while ( true )
{
$read = array($fp);
$write = NULL;
$except = array($fp);
//Wait for up to 5 second to get something from the server
if (false === ($num_changed_streams = stream_select($read, $write, $except, 5)))
{
// It timed out!
fclose($fp);
print( "Socket internal error\n" );
throw new Exception( "Socket internal error" );
}
if( empty($read) ) //It must be an excecption instead...
{
// We got a socket error
fclose($fp);
print("Socket error\n");
throw new Exception( "Socket Error" );
}
print( var_export( array( $read, $write, $except), true )."\n" );
print( "Num changed streams: $num_changed_streams\n" );
if ( $num_changed_streams == 0 )
{
// nothing changed int he stream, we hit a timeout!
fclose( $fp );
print( "Socket timeout\n" );
throw new Exception( "Socket timeout" );
}
//We're ready to read.
$chunk = fread($fp, 1024);
if( $chunk === FALSE )
{
print("fread failed\n");
throw new Exception("fread failed");
}
print( "Chnk: ".var_export( $chunk, true )."\n" );
$buffer.= $chunk;
if ( (strlen($chunk)>0) && (ord($chunk[strlen($chunk)-1])==0) ) break;
}
fclose($fp);
Worked this one out. As per the php documentation at http://au.php.net/manual/en/function.stream-select.php
This 0 length string return case was being triggered when the remote end went away, but was not being caught in the break check.
Fix is to test the length of the return from fread, and if it is 0 then break out of the loop.