asynchronous php and real-time messaging
DESCRIPTION
With every major browser supporting WebSockets, HTML 5 has changed how we handle client to server communications. The high demand for real time client and server messaging has developers flocking away from PHP to languages such as Node.js. In this session we'll explore the libraries and extensions that make Asynchronous PHP possible and analyze the performance differences with Node.js. In addition we'll identify use cases and walk through examples of how Asynchronous PHP can handle everything from WebSockets and Message Queues to MySQL.TRANSCRIPT
asynchronous
steve rhoadestwitter+github @steverhoades
Asynchronous PHP
page scraper$urls = ['www.amazon.com', ...]; !foreach($urls as $url) { $content = file_get_contents( "http://$url", false, $context );}
request timewww.amazon.com: 0.80137www.reddit.com: 0.21584www.hackernews.com: 1.80921www.google.com: 0.29365www.yahoo.com: 1.39217!
Total time in seconds: 4.51274
time
call 2
Netw
ork
call 1Ne
twork
call 3
Netw
ork
blocking i/o
* Assuming ~1GB/sec SSD source: https://gist.github.com/jboner/2841832
Send 1K bytes over Gbps network 0.01 ms
Read 4K randomly from SSD* 0.15 ms
Read 1 MB sequentially from memory 0.25 ms
Round trip within same datacenter 0.5 ms
Read 1 MB sequentially from SSD* 1 ms
Disk seek 10 ms
Read 1MB sequentially from disk 20 ms
Send packet CA->Netherlands->CA 150 ms
I/O is slow.
var urls = ['http://www.amazon.com', ...];!for(var i in urls) { http.get(urls[i], function(response) { var str = ''; response.on('data', function (chunk) { str += chunk; });! response.on('end', function () { // do something with data }); });}
page scraper
www.amazon.com: 0.75559www.reddit.com: 0.33153www.hackernews.com: 0.57661www.google.com: 0.48226www.yahoo.com: 0.23333!
Total time: 0.76421
request time
call 2call 1 call 3create
create
non-blocking i/o
create
time
resp
resp
resp
reqreq
req
$urls = ['www.amazon.com',...];!foreach($urls as $ip => $url) { // create a stream that returns immediately // STREAM_CLIENT_CONNECT _MUST_ be passed // STREAM_CLIENT_ASYNC_CONNECT says create connection // asynchronously $socket = stream_socket_client( "tcp://$ip:80", $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT ); // set stream as non-blocking with 0, default is 1 stream_set_blocking($socket, 0);! // sockets = read streams, requests = write streams $sockets[(int) $socket] = $socket; $requests[(int) $socket] = $socket; }
non-blocking i/o
while(!empty($sockets)) { //run loop $read = $sockets; $write = $requests; $except = NULL; ! // check the multiplexer for stream events $ready = stream_select($read, $write, $except, 0); foreach($read as $readFd) { // .. read } foreach($write as $writeFd) { // .. write } }
$urls = ['www.amazon.com',...];
foreach($urls as $ip => $url) { // create a stream that returns immediately // STREAM_CLIENT_CONNECT _MUST_ be passed // STREAM_CLIENT_ASYNC_CONNECT says create connection // asynchronously $socket = stream_socket_client( "tcp://$ip:80", $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT
// set stream as non-blocking with 0, default is 1 stream_set_blocking($socket, 0);
// sockets = read streams, requests = write streams $sockets[(int) $socket] = $socket; $requests[(int) $socket] = $socket;
non-blocking i/o
non-blocking i/owww.reddit.com: 0.18858www.hackernews.com: 0.37317www.google.com: 0.10562www.yahoo.com: 0.10172www.amazon.com: 0.68584!
Total time: 0.69041(PHP Blocking Example: 4.51274)
event-drivennon-blocking i/o
Igor Wiedler
Christopher Boden#reactphp
igorw/evenementEventEmitter
on($event, callable $listener)$this->on('pipe', array($this, 'handleEvent'));
emit($event, array $arguments = [])$this->emit('data', array($data, $this));
once($event, callable $listener)$this->once('init', array($this, 'configure'));
event loop
service request
dispatcher
ticknextTickfutureTick
demultiplexertimers & streams
event handlercallback
even
t loop
periodic timers$loop->addPeriodicTimer(1, function($timer) { echo "Yes, I am annoying =)" . PHP_EOL;});!
one off timers$loop->addTimer(1, function($timer) { echo "I'm a one off timer.” . PHP_EOL;});
interval in seconds
callback Timer object
// cancel that annoying timer $timer->cancel(); });
timers
STREAMSStream($stream, $loop) events:
data, close, error, drain
$readStream = new Stream(fopen($file,"r"),$loop);$readStream->on('data',function($data, $stream) { //do something with $data});
readableStream
writeableStreamemit->(‘pipe’)
STREAMS & PIPING
on->(‘drain’)resume()on->(‘data’)
write($data)end()
end()pause()
if $data > $limit
$readStream->pipe($writeStream);
Promises/A
states
“A promise represents the eventual value returned from the single completion of an operation.”
“once fulfilled or rejected the promise’s value shall not be changed”
pending, fulfilled, rejected
http://wiki.commonjs.org/wiki/Promises/A
working with a Promise $loop = React\EventLoop\Factory::create(); $factory = new React\Dns\Resolver\Factory(); $dns = $factory->create('8.8.8.8', $loop);! $dns ->resolve('github.com') ->then(function ($ip) { echo "Host: $ip\n"; } ); $loop->run();
Deferred
working with Promise\all()
$promises = array( $file1->read(), $file2->read());!Promise\all($promises)->then(function($v){});Promise\race($promises)->then(function($v){});Promise\some($promises, 4)->then(function($v){});
$file1 = new file('test_file.txt', "r", $loop);$file2 = new file('test_file2.txt', "r", $loop);
Server events:connection, error
SOCKETS
Connectionextends Stream
events: data, close, error, drain
$loop = React\EventLoop\Factory::create(); $socket = new React\Socket\Server($loop); !$socket->on('connection', function($conn) { echo $conn->getRemoteAddress() . " connected" . PHP_EOL; $conn->on('data', function($data) { echo "Data received for connection" . PHP_EOL; }); }); !$socket->listen(4000); $loop->run();
page scraper$factory = new React\Dns\Resolver\Factory();$dns = $factory->create('8.8.8.8', $loop);$urls = ['www.amazon.com',...];$msg = "GET / HTTP/1.0\r\nAccept: */*\r\n\r\n";!foreach($urls as $url) { $connector = new React\SocketClient\Connector($loop, $dns); $connector->create($url, 80)->then( function (React\Stream\Stream $stream) use ($msg){ $stream->write($msg); $stream->on('data', function($data, $stream) { // buffer data } ); $stream->on('end', function($stream) { // do something with the data $stream->close(); }); } );}$loop->run();
request timewww.reddit.com: 0.47400www.hackernews.com: 0.41715www.google.com: 0.16216www.yahoo.com: 0.15773www.amazon.com: 0.65287!
Total time: 0.69455(PHP Blocking Example: 4.51274)
Messaging
Messaging Techniques
polling long polling (hanging GET)
pre-
Messaging Techniques
long polling (hanging GET)
streaming
pre-
Server Sent Eventssupported by all major browsers
• automatically re-connects • uni-directional • send arbitrary events
var source = new EventSource('stream.php');
source.addEventListener('message', function(e) { console.log(e.data);}, false);
demoserver sent events
WebSocketssupported by all major browsers
• new URI schemes ws and wss • bi-directional, full-duplex • send messages independently
WebSocketssupported by all major browsers
• multiplayer games • chat applications • social streams • auctions
WebSockets APIsupported by all major browsers
var ws = new WebSocket("ws://www.websockets.org"); ws.onopen = function(e) { console.log("Connection open ..."); }; ws.onmessage = function(e) { console.log( "Received Message: " + e.data); }; ws.onclose = function(e) { console.log("Connection closed."); }; ws.send("Hello WebSockets!"); ws.close();
RatchetWebSocket Support for • HTTP Server • handles WebSockets • supports the WAMP 1.0 protocol
$socketServer = new React\Socket\Server($loop);$socketServer->listen('8080', '0.0.0.0');$websocketServer = new IoServer( new HttpServer( new WsServer( $myWsApp ) ), $socketServer, $loop);
RatchetWebSocket Server
onOpen($conn)
onClose($conn)
onError($from, $error)
onMessage($from, $msg)
demoweb sockets
WAMPWeb Application Messaging Protocol
autobahn.js
Remote Procedure Calls Publish & Subscribe
$socketServer = new React\Socket\Server($loop);$socketServer->listen(8080, '0.0.0.0');$wampServer = new IoServer( new HttpServer( new WsServer( new WampServer( $myWampApp ) ) ), $socketServer, $loop);
Ratchet
onCall($conn, $id, $topic, $params)
onPublish($conn, $topic, $event)
onSubscribe($conn, $topic)
onUnsubscribe($conn, $topic)
Ratchet$conn->callResult($id,$this->playerData);wamp connection
$topic->broadcast($event, array($conn->WAMP->sessionId));topic$conn->callError($id, $topic, 'You are not allowed to make calls');
demoWAMP
results based on: http://philsturgeon.uk/blog/2013/11/benchmarking-codswallop-nodejs-v-php
OTHER INTERESTING LIBRARIES:
AMP: dispatch blocking calls to worker threads.https://github.com/rdlowrey/Amp
INTERESTING READS:COOPERATIVE MULTI-TASKING USING COROUTINES IN PHPhttp://bit.ly/1nTAV4e - nikic
Q & A
http://socketo.me http://reactphp.org
Code Examples:https://github.com/steverhoades/drupalcampla
steve rhoadestwitter+github @steverhoades