Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting peer disconnect (EOF) with PHP socket module

Tags:

php

sockets

eof

I'm having a weird issue with PHP's sockets library: I do not seem to be able to detect/distinguish server EOF, and my code is helplessly going into an infinite loop as a result.

Further explanation below; first of all, some context (there's nothing particularly fancy going on here):

<?php

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_connect($socket, '127.0.0.1', 8081);

for (;;) {

    $read = [$socket];
    $except = NULL;
    $write = [];

    print "Select <";
    $n = socket_select($read, $write, $except, NULL);
    print ">\n";

    if (count($read)) {

        print "New data: ";

        #socket_recv($socket, $data, 1024, NULL);
        $data = socket_read($socket, 1024);

        print $data."\n";

    }

    print "Socket status: ".socket_strerror(socket_last_error())."\n";

}

The above code simply connects to a server and prints what it reads. It's a cut-down version of what I have in the small socket library I'm writing.

For testing, I'm currently using ncat -vvklp 8081 to bind a socket and be a server. With that running, I can fire up the code above and it connects and works - eg, I can type in the ncat window, and PHP receives it. (Sending data from PHP is working too, but I've excluded that code as it's not relevant.)

However, the moment I ^C ncat, the code above enters a hard infinite loop - and PHP says there's no error on the socket.

I am trying to figure out where the button is that whacks PHP upside the head and makes it realize that the peer has disconnected.

  • socket_get_status() is a great misnomer - it's an alias for stream_get_meta_data(), and it doesn't actually work on sockets!

  • feof() similarly spouts Warning: feof(): supplied resource is not a valid stream resource.

I can't find a socket_* function for detecting peer EOF.

One of the PHP manual notes for socket_read() initially dissuaded me from using that function so I used socket_recv() instead, but I eventually tried it just in case - but no dice; switching the receive call has no effect.

I have discovered that watching the socket for writing and then attempting to write to it will suddenly make PHP go "oh, wait, right" and start returning Broken pipe - but I'm not interested in writing to the server, I want to read from it!

Finally, regarding the commented part - I would far prefer to use PHP's builtin stream functionality, but the stream_* functions do not provide any means for handling asynchronous connect events (which I want to do, as I'm making multiple connections). I can do stream_socket_client(... STREAM_CLIENT_ASYNC_CONNECT ...) but then cannot find out when the connection has been established (6yo PHP bug #52811).

like image 269
i336_ Avatar asked Nov 09 '22 09:11

i336_


1 Answers

Okay, I figure I might as well turn the comments above into an answer. All credit goes to Ryan Vincent for helping my thick head figure this out :)

socket_recv will return 0 specifically if the peer has disconnected, or FALSE if any other network error has occurred.

For reference, in C, recv()'s return value is the length of the new data you've just received (which can be 0), or -1 to indicate an error condition (the value of which can be found in errno).

Using 0 to indicate an error condition (and just one arbitrary type of error condition, at that) is not standard and unique to PHP in all the wrong ways. Other network libraries don't work this way.

You need to to handle it like this.

$r = socket_recv($socket, $buf, $len);

if ($r === FALSE) {

   // Find out what just happened with socket_last_error()
   // (there's a great list of error codes in the comments at
   // http://php.net/socket_last_error - considering/researching
   // the ramifications of each condition is recommended)

} elseif ($r === 0) {

   // The peer closed the connection. You need to handle this
   // condition and clean up.

} else {

   // You DO have data at this point.
   // While unlikely, it's possible the remote peer has
   // sent you data of 0 length; remember to use strlen($buf).

}
like image 89
i336_ Avatar answered Nov 15 '22 05:11

i336_