Damaged Organic
Damaged Organic

Reputation: 8467

Socket server + Event source magic disconnect

I've successfully implemented next scheme: a php socket server script that broadcasts message to all connected client scripts, and those client scripts are listened by event-source on the front-end script. So, basically, scheme is: server.php -> client.php -> front.php.

But I've been struggling with this part from client.php:

while(TRUE)
{
    $read   = array();
    $read[] = $client_socket;

    if( socket_select($read, $write, $except, 10) === FALSE ){
        $errorcode = socket_last_error();
        $errormsg = socket_strerror($errorcode);
        file_put_contents("select_result.log", "Could not listen on socket : [$errorcode] $errormsg".PHP_EOL);
    }

    if( !($client_message = @socket_read($client_socket, 1024)) ){
        $errorcode = socket_last_error();
        $errormsg = socket_strerror($errorcode);
        file_put_contents("read_result.log", "Couldn't read socket: [$errorcode] $errormsg".PHP_EOL, FILE_APPEND);
    }

    if($client_message == FALSE) {
        $client_message  = "event: keep_alive" . PHP_EOL
                         . "data: keep_alive" . PHP_EOL . PHP_EOL;
    }

    echo $client_message;

    if( ob_get_level() > 0 ) ob_flush();
    flush();
}

When a open front.php script (which uses event-source) in browser, it works fine, BUT it also somehow detects disconnect, when I close or reload front.php in browser window. Here is part from server.php that detects disconnect:

if( !($client_input = @socket_read($client, 1024))) {
    $errorcode = socket_last_error();
    $errormsg = socket_strerror($errorcode);
    echo "$errorcode".PHP_EOL;
}

if ( $client_input === FALSE ) {
    $socket_key = array_search($client, $client_sockets);
    socket_getpeername($client, $client_address, $client_port);
    socket_close($client);//?!!!
    unset($client_sockets[$socket_key]);
    echo "[$client_address : $client_port disconnected]".PHP_EOL;
}

As you see, this happens when data cannot be read from client. Theoretically, when I close front.php, client.php should still be working in an endless cycle on background, as I haven't got any loop breaks, but - surprise! - it stops. Experimentally, I've found that this is happening due to ob_flush() function. When it's commented, server.php does not detect disconnect (browser window close/reload). How could it be? ob_flush() does not return any value, according to manual. No errors detected as well... I don't know what to think, and I can't find why this happening. Please, help.

Upvotes: 3

Views: 1098

Answers (2)

Darren Cook
Darren Cook

Reputation: 28928

I am assuming you are using Apache or something in front of client.php. What happens when your front.php runs var es = new EventSource('client.php') is that a dedicated TCP/IP socket is created between the browser and Apache. Apache runs PHP, telling it to load and run client.php. (And client.php then creates a socket to listen to messages from server.php.)

When the browser is shut (or you call es.close(), or for some reason the socket connection between browser and Apache gets lost), then Apache will immediately(*) close down the PHP process (the one running client.php). And when that PHP process disappears, the socket between client.php and server.php will get closed (either immediately, or the next time server.php tries to read/write to it).

*: usually "immediately". Sometimes if a socket is not closed cleanly it can hang around for a little while (a matter of seconds, never more than a minute).

I think your ob_flush() observation is a bit of red herring; my guess would be that by not calling ob_flush() there is something stuck in a buffer, which means Apache keeps the PHP process alive until some time-out expires. Incidentally, I use the @ob_flush();@flush() idiom; the @ before ob_flush() is basically doing what your ob_get_level() check is doing (but I vaguely remember hearing there is a situation where that check is not always reliable).

Upvotes: 1

Daniel W.
Daniel W.

Reputation: 32310

Flushing the output buffer to the browser does not work (anymore) as expected with recent version of Chrome/Firefox.

You would need some kind of register_shutdown_func() which does not work stable aswell for the purpose you want it.

It's generally very unstable to put client/server in a PHP script loaded through the browser due to the design of the HTTP protocol. It is not designed to keep connections alive and send messages the way IRC for example works. HTTP connections can close due to many reasons mostly after 30-60 seconds.

I suggest you to use the PHP command line version for the server and JavaScript for the client purpose.

Also the way you load PHP (mod_php in apache?) affects the whole thing. How many threads/forks of PHP/Apache do you open, how many connections does every process hold,..

Some webservers kill any scripts when the page finished loading after some seconds, its hard to debug and hard to get stable.

Try WebRTC / WebSockets in JavaScript, it's way better for this and integrates better into any HTML5 site.

Upvotes: 0

Related Questions