php stream_get_contents hangs at the end of the stream
Asked Answered
C

2

7

Solution at the end of the question

I am writing a PHP application that sends a message to a server and then reads a response back in using stream_get_contents. I communicate with the same server in an android app in the same way. The android app works fine and responds quickly, however the PHP hangs when reading the response back from the server.

In the code sample below, I have set a tiny buffer size of 5 bytes to test a theory. If I remove this buffer size it hangs, however with the 5 byte size it only hangs on the last pass through the loop:

stream_set_timeout($this->socket, 10); //10 seconds read timeout

while (!feof($this->socket)) {
    $breakOut = false;

    echo 'Reading response'.time().'<br/>';
    $data = stream_get_contents($this->socket, 5);
    echo 'Read response'.time().'<br/>';

    if ($data === false) {
        $this->latestErrStr = "Timed out waiting for a response.";
        return false;
    } else {
        $index = strpos($data, chr(3));

        if ($index !== FALSE){
            $breakOut = true;
            $data = substr($data, 0, $index);
        }

        $response .= $data;
    }

    $stream_meta_data = stream_get_meta_data($this->socket);

    //If we have no EOF marker then break if there are no bytes left to read
    if($breakOut || $stream_meta_data['unread_bytes'] <= 0) {
        break;
    }
}

The output is as follows:

Reading response1387463602
Read response1387463603
Reading response1387463603
Read response1387463603
Reading response1387463603
Read response1387463603
Reading response1387463603
Read response1387463603
Reading response1387463603
Read response1387463603
Reading response1387463603
Read response1387463603
Reading response1387463603
Read response1387463603
Reading response1387463603
Read response1387463603
Reading response1387463603
Read response1387463603
Reading response1387463603
Read response1387463603
Reading response1387463603
Read response1387463603
Reading response1387463603
Read response1387463603
Reading response1387463603
Read response1387463623

As you can see there is a 10s delay between the last two lines, but no noticeable delay between the others.

Also for your information I use an ETX marker (3) to signify the end of a message, so I also stop if I hit this rather than just the end of the stream.

Am I doing something wrong? Is there a better way of doing this?

Thanks in advance...

Edit: Just to be clear, the above code is only expecting one message response. It does not care about any data that comes back after it has received an ETX byte.

Edit2: Hangs have been seen of up to 40 seconds now. It doesn't appear to be fixed to 10 seconds, but it weirdly seems to be nice round numbers every time.

Solution (thanks to chathux):

stream_get_contents($stream, $bytes) will block until it receives $bytes bytes or the timeout expires. This means my code was reaching the end and trying to read 5 bytes (which didn't exist), it was therefore waiting 10s before giving up.

As I know the minimum size of a message coming back to me is 49 bytes, I first read those 49 bytes (blocking until I get them or 10s expires) in order to populate the stream_get_meta_data's unread_bytes field. Once I have this I dynamically adjust the buffer size to min(16*1024, unread_bytes) so I either read 16k at a time or all of the remaining bytes, whichever is smaller. In my case this usually only means two goes through the loop as messages are often tiny (49 bytes + payload).

The system now hangs for approximately 3 seconds instead of 10, but it hangs waiting for the initial few bytes to arrive (rather than at the end) which can be put down to network latency and other normal factors.

Cheng answered 19/12, 2013 at 14:53 Comment(0)
M
5

documentation says "stream_get_contents() operates on an already open stream resource and returns the remaining contents in a string, up to maxlength bytes and starting at the specified offset."

so when you provide 5 as maxlength it will only read up to five bytes and continues. if it cant read up to 5 bytes it`ll wait and expires in 10 seconds which you have mentioned in stream_set_timeout

example :

//server side statement<br/>
$data = stream_get_contents($this->socket, 5);

//corresponding client code<br/>
fwrite($client, "1234");

in above case server will wait till you write one more byte fwrite($client, "5");

Mazonson answered 1/1, 2014 at 6:56 Comment(0)
V
1

I suggest you just use the sleep($seconds) function, or even the usleep($nanoseconds) function. The timeout is set for the stream itself, not for each stream_get_contents

Vagrant answered 19/12, 2013 at 14:57 Comment(8)
Do you mean use sleep for the timeout? How would that work? stream_set_timeout was meant as a backup in case the server did not respond in a timely fashion so the user wasn't left waiting.Cheng
You should still use stream_set_timeout just in case the server hangs. But between each request/response you could use sleep(10) to let the script wait 10 seconds before processing the next request/responseVagrant
The code is only waiting for one response, the loop was just to make sure it had received the whole response before carrying on. Surely I don't need a sleep between reading parts of a message? I will try it anyway and see if it helps. Have updated the question to make it clear the code is only waiting for one message.Cheng
Using usleep didn't seem to make a difference.Cheng
Be aware that usleep is in microseconds, so sleep(1000000) is sleeping for 1 second.Vagrant
Fair play, I did make that mistake but that still didn't solve the problem sorry :(. If anything it added to the hang as I was waiting between each 5 bytes.Cheng
What about closing the stream in the break-out section, maybe it's some weird php-stream behaviour?Vagrant
The socket is closed just after that code snippet. As I'm checking the time before and after the stream_get_contents line it seems that is where the delay is. I'm not sure if the problem can lie in that section of code. It's more likely to be that I've configured the socket wrong or an external problem (e.g. server) I would have thought?Cheng

© 2022 - 2024 — McMap. All rights reserved.