3

When trying to write to the client, the message is getting buffered, and in some cases, it's not being written at all.

CURRENT STATUS:
When I telnet into the server, the Server Ready: message is readily printed as expected.

When I send random data (other than "close"), the server's terminal nicely shows progress every second, but the clients output waits until after all the sleeping, and then prints it all at once.

Most importantly, when sending "close", it just waits the obligatory second, and then closes without ANY writeout in the client.

GOAL:
My main goal is for a quick message to be written to the client prior closing a connection.

CODE:

// server.php
$loop = React\EventLoop\Factory::create();

$socket = new React\Socket\Server($loop);
$socket->on('connection', function ($conn)
{
    $conn->write("Server ready:\n");

    $conn->on('data', function ($data) use ($conn)
    {
        $data = trim($data);

        if( $data == 'close')
        {
            $conn->write("Bye\n");
            sleep(1);
            $conn->close();
        }

        for ($i = 1; $i<5; $i++) {
            $conn->write(". ");
            echo '. '; 
            sleep(1);
        }

        $conn->write(".\n");
        echo ".\n";

        $conn->write("You said \"".$data."\"\n");

    });
});
$socket->listen(1337, '127.0.0.1');
$loop->run();

SUMMARY:
Why can't I get anything written to the client before closing?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
mOrloff
  • 2,547
  • 4
  • 29
  • 48
  • BTW, some of this seems a little over my head, but is [**this issue**](https://github.com/reactphp/react/issues/277) the same thing I'm running in to? – mOrloff Mar 26 '14 at 20:05
  • Well, when using `$conn->end()` instead of `$conn->close()`, that'll at least give the writeout before closing, so I'm stoked about that. As gfor how to make the writeout more, truly, async ... still don't know :) – mOrloff Mar 29 '14 at 00:20
  • Did you ever figure this out? I imagine there is some way to override Reacts buffer, and just flush immediately? Would really like to be able to write a method $conn->flush() which will flush the socket buffer sending it to the client while keeping the connection open. I'm not sure if that is possible or the way sockets work? – newms87 Nov 24 '16 at 02:43
  • It came in rather later than you posted this, but did the answer below help at all, @mOrloff? – halfer Sep 06 '17 at 16:23

1 Answers1

8

The problem your are encountering is because you are forgetting about the event loop that drives ReactPHP. I ran into this issue recently when building a server and after following the code around, I found 2 things out that should help you solve your problem.

  1. If you close the connection after writing to it, it simply closes the connection before it can write. Solving this issue can help you fix the next issue... The correct call for writing something to the client, THEN closing the connection is called $conn->end('msg'); If you follow this chain of code the behaviour becomes clear; First it basically writes to the connection just as if you ran $conn->write('msg'); but then it registers a new event handler for the full-drain event, this handler simple calls $conn->close();; the full-drain event is only dispatched when the write buffer is completely emptied. So you can see that the use of end, simply waits to write before it closes the connection.

The drain and full-drain are only dispatched after writing to a stream. full-drain occurs when the write buffer is completely empty. drain is dispatched after the write buffer is emptied past its softLimit which by default is 2048 bytes.

  1. The reason your writes are not making it through is because $conn->write('msg') only adds the string to the write buffer; it does not actually write. The event loop needs to run before it will be given time to write. Your use of sleep() is incorrect with react because this blocks the call at that line. In react you don't want to block any code from executing. If you are done a task, then let your function return, and code execution returns to the react main event loop. If you wish to have things run on certain intervals, or simply on the next iteration of the loop, you can schedule callbacks for the main event loop with the use of $loop->addTimer($seconds, $callback), $loop->addPeriodicTimer($seconds, $callback), $loop->nextTick($callback) or $loop->futureTick($callback)

Ultimately it is because you are programming without acknowledging that we are still in a blocking thread. Anything your code that blocks, blocks the entire react event loop, in turn blocking everything that react does for you. Give up processing time back to the loop to ensure it can do the reads/writes that you have queued up. You only need on iteration of the loop to occur for the write buffer to begin emptying (depending on the size of the buffer it may or may not write it all out)

If you're goal here is just to fix the connection closing bit, switch your call to $conn->end('msg') instead of the write -> close. However I believe that the other loop you have printing the dots also does not behave in the way that I think you expect/desire it to work. As it's purpose is not as clear, if you can tell me what your goal was for it I can possibly help you restructure that step as well.

Method
  • 353
  • 3
  • 10