Realtime Communication Techniques with PHP
-
Upload
waterspout -
Category
Technology
-
view
80.443 -
download
0
Transcript of Realtime Communication Techniques with PHP
Realtime Communication Techniques with PHP
Scott Mattocks & Chris Lewis, OnForce, Inc.
Agenda Introductions Overview
– User expectations– Problems with delivery
Techniques– Refresh– Short Polling– Long Polling– WebSockets
Q&A
Introductions Scott Mattocks
– Senior PHP developer at OnForce, Inc.– Contributor to PEAR– Author of Pro PHP-GTK
Chris Lewis– Senior PHP developer at OnForce, Inc.– 6 years of enterprise PHP experience – 12 years of front-end web development
experience Both are contributors to the WaterSpout Real-time
Communication Server
Overview
Example: http://www.spoutserver.com
– Click “John Locke” in the Demos section
Overview
Users expect data to be delivered more quickly and seemlessly
– Gmail changed the way user expect to interact with websites
Loading an entire page to show a small change is slow and wasteful
Problems with delivery
The web runs (for the most part) on HTTP
– HTTP is one way
– User asks for data
– Server sends data back
– The server can't do anything without the user asking first
Problems with delivery Timeliness
– There is a gap between arrival of the data on the server and notification of the user.
– We want to reduce the gap without killing the servers.
Client Server
New Data
New DataBlack out
Problems with delivery Efficiency
– How much of the transfered data is important to the user?
– Data transfer:• X bytes sent for request (GET headers)• Y bytes sent for response (body and
headers)• Z bytes of new data• Z / (X + Y) = efficiency
– We want try to send only the data that has changed and that is important to the user
Problems with delivery
Scalability– What kind of load are we causing on
the server?– Are we holding resources (i.e.
database connections) that could be used to server other users?
– We want to do more with less
Solutions
Refresh the page
Short Polling
Long Polling
– 1 server
– 2 servers
WebSockets
Solutions
@gnomeboy wantto come over forsome cookies?
@gnomegirlhellz yeah!
Example: Simple twitter feed style page
Solutions Page consists of a wrapper around message content
– Avg message size = 100 bytes– Avg page size (without images) = 4 KB– Avg request headers = 410 bytes– Avg response headers = 210 bytes
(JavaScript code examples use Prototype)
Refresh
The same as if the user hit F5
– Reloads the entire page
– New page load adds the new message
No constant connection to the server
– Black out periods between requests
Refresh database
web server web server
Refresh
Difficulty
– 1 out of 10
– Use HTML or JavaScript
<meta http-equiv="refresh" content="5" />
setTimeout('window.location.reload(true)', 5000);
Refresh
Timeliness
– Timeliness of data depends on the refresh rate
– There is an additional delay for the round trip to the server
• Includes time to send request, process request, and send response
Refresh Efficiency (assume 10 total requests, 1 new message)
– Data transfer:
• 410 bytes sent for each request
• 4210 bytes sent for each response, 4310 for new message response
• 100 bytes of new data
• 100 / ((410 + 4210) * 10 + 100) = .0021 per message
– Server resources used
• 1 HTTP process per request (10 total)
• 1 Database connection per request (10 total)
Short Polling Ask the server for updates since the last time you
asked
– Like a road trip with a 3 year old
– Are we there yet? Are we there yet?
No constant connection to the server
– Black out periods between requests
– Cursor required to prevent data loss
Short Pollingdatabase
web server web server
Short Polling Difficulty
– 5 out of 10
– AJAX
• Background request to the server checks for new messages at a timed interval
• New messages are added to DOM by JavaScript
Short Pollingfunction poll_server() {
new Ajax.Request(this.urlPolling, {method: 'post',parameters: data,onSuccess: this.successHandler,onFailure: this.handleFailure,onException: this.handleException
});}
setInterval('poll_server()', 5000);
Short Polling
function successHandler(trans, messages_div) { if (trans.responseText) { var json = trans.responseText.evalJSON(); if (json.message) { var msg = json.message + '<br />'; $(messages_div).insert(msg); } }}
Short Polling
function successHandler(trans, messages_div) { if (trans.responseText) { var json = trans.responseText.evalJSON(); if (json.message) { var msg = json.message + '<br />'; $(messages_div).insert(msg); } }}
Short Polling Timeliness
– Timeliness of data depends on the refresh rate
– There is an additional delay for the round trip to the server
• Includes time to send request, process request, and send response
Short Polling Efficiency (assume 10 total requests, 1 new message)
– Data transfer:
• 410 bytes sent for each request
• 210 bytes sent for each empty response, 310 for new message response
• 100 bytes of new data
• 100 / ((410 + 210) * 10 + 100) = .0158 per message
– Server resources used
• 1 HTTP process per request (10 total)
• 1 Database connection per request (10 total)
Long Polling - 1 server Ask the server for updates since the last time you
asked
– Server does not respond until a new message has arrived
No constant connection to the server
– Black out periods between sending response and next request
– Cursor required to prevent data loss
Long Polling – 1 server
database
web server web server
Long Polling – 1 server Difficulty – Client side
– 5 out of 10
– AJAX
• Background request to the server checks for new messages and waits for responses
• New messages are added to DOM by JavaScript
Long Polling – 1 server Difficulty – Server side
– 5 out of 10
– Continuously loop checking for new messages
– Break out of loop when new message arrives
Long Polling – 1 server
function poll_server() {
new Ajax.Request(this.urlPolling, {
method: 'post',
parameters: data,
onSuccess: this.successHandler,
onFailure: this.handleFailure,
onException: this.handleException
});
}
document.observe('dom:loaded', poll_server);
Long Polling – 1 server
function successHandler(trans, messages_div) { if (trans.responseText) { var json = trans.responseText.evalJSON(); if (json.message) { var msg = json.message + '<br />'; $(messages_div).insert(msg); } }
poll_server();}
Long Polling – 1 server
<?php$query = 'SELECT message FROM messages WHERE dateadded > ?';$stmt = $pdo->prepare($query);
while (true) { $message = $stmt->execute($cursor_date);
if (!empty($message)) { echo json_encode($message); break; }}?>
Long Polling – 1 server Timeliness
– Near real-time– As soon as a message shows up on the
server, it is sent to the waiting client– Black out period between sending response
and making new request can add some delay
Long Polling – 1 server Efficiency (1 request, 1 new message)
– Data transfer:• 410 bytes sent for request• 310 bytes for new message response• 100 bytes of new data• 100 / (410 + 310) = .138 per
message– Server resources used
• 1 HTTP process• 1 Database connection
Long Polling – 2 servers 2 servers work together to use fewer resources
– Main web server handles initial page request
– Main server informs polling server when new message arrives
– Polling server informs client No constant connection to server
– Black out periods between sending response and next request
– Cursor required to prevent data loss
Long Polling – 2 servers
web server polling server
HTTP
XHR over HTTP
Long Polling – 2 servers Difficulty – Client side
– 5 out of 10– AJAX
• Background request to the server checks for new messages and waits for responses
• New messages are added to DOM by JavaScript
Long Polling – 2 servers Difficulty – Server side
– 8 out of 10– Web server makes cURL call to polling
server when a new message is added– Polling server determines who message is
for and sends the response to the waiting connection
Long Polling – 2 servers
function poll_server() {
new Ajax.Request(this.urlPolling, {
method: 'post',
parameters: data,
onSuccess: this.successHandler,
onFailure: this.handleFailure,
onException: this.handleException
});
}
document.observe('dom:loaded', poll_server);
Long Polling – 2 servers
function successHandler(trans, messages_div) { if (trans.responseText) { var json = trans.responseText.evalJSON(); if (json.message) { var msg = json.message + '<br />'; $(messages_div).insert(msg); } }
poll_server();}
Long Polling – 2 serversMain Web Server
Polling Server
<?php$query = 'INSERT INTO messages VALUES (?, ?)';$stmt = $pdo->prepare($query);$stmt->prepare($message, $date);$polling_server->send($message);?>
<?phpforeach ($this->waiting as $connection){ $connection->respond($message);}?>
Long Polling – 2 servers Timeliness
– Near real-time– As soon as a message shows up on the
server, it is sent to the waiting client– Black out period between sending response
and making new request can add some delay
Long Polling – 2 servers Efficiency (1 request, 1 new message)
– Data transfer:• 410 bytes sent for request• 310 bytes for new message response• 100 bytes of new data• 100 / (410 + 310) = .138 per
message– Server resources used
• 1 HTTP process on polling server per message
• 0 Database connections
WebSockets
According to http://en.wikipedia.org/wiki/WebSocket:
„WebSockets is a technology providing for bi-directional, full-duplex communications channels, over a single Transmission Control Protocol (TCP) socket, designed to be implemented in web browsers and web servers”
WebSockets WebSockets allow server to push data to client Connection established via client-initiated
handshake After handshake, both the client and the server
can send and recieve data
– Server can send data without the client asking for it first
WebSockets Builds off of 2 server long polling
– Main web server communicates with event server
Constant connection between client and server
– No black out periods
– No need for cursor
WebSockets
web server WebSocket server
JSON via WebSockets
WebSockets Difficulty – Client side
– 5 out of 10– WebSocket API
• Connection established• onMessage events fired when new
data comes in from the server• New messages are added to DOM by
JavaScript
WebSockets Difficulty – Server side
– 8 out of 10– Web server makes cURL call to WebSocket
server when a new message is added– WebSocket server determines who
message is for and sends the response to the waiting connection
WebSockets
function poll_server() {
var socket = new WebSocket('ws://example.com/');
socket.onmessage = function (response) {
successHandler(response, 'message_errors');
}
}
document.observe('dom:loaded', poll_server);
WebSockets
function successHandler(trans, messages_div) { if (trans.data) { var json = trans.data.evalJSON(); if (json.message) { var msg = json.message + '<br />'; $(messages_div).insert(msg); } }}
WebSocketsMain Web Server
WebSocket Server
<?php$query = 'INSERT INTO messages VALUES (?, ?)';$stmt = $pdo->prepare($query);$stmt->prepare($message, $date);$polling_server->send($message);?>
<?phpforeach ($this->waiting as $connection){ $connection->respond($message);}?>
WebSockets Timeliness
– Real-time– As soon as a message shows up on the
server, it is sent to the waiting client– No black out period
WebSockets Post Handshake Efficiency
– Data transfer:• 0 bytes sent for request• 100 bytes for new message response• 100 bytes of new data• 100 / 100 = 1.000 per message
– Server resources used• 1 process on WebSocket server• 0 Database connections
WebSockets
Efficiency Demo– http://spoutserver.com/demos/compare
Comparison– Refresh: .0021– Short Polling: .0158– Long Polling: .138– WebSockets: 1.00
WaterSpout Lightweight HTTP server
– Static files– Dynamic content (PHP)
Best used as part of event-driven server pair– Can function as both ends
Handles WebSockets and long polling Written in PHP
WaterSpout Two types of connections
– Listeners– Updates
When an update comes in the appropriate listeners are notified
– Custom controllers define which listeners should be notified
WaterSpout - Listeners<?php
public function listen() {
// Handle cursor for long polling fall back.
// ...
$this->dispatcher->add_listener($this);
}
?>
WaterSpout - Dispatcher<?php/* Called every .25 seconds on all waiting listeners */public function process_event(Controller $mover = null) { $key = array_search((int) $this->_cursor, array_keys(self::$_commands));
if ($key === false && !is_null($this->_cursor)) { return; }
$commands = array_slice(self::$_commands, $key); if (empty($commands)) { return; }
$response = new HTTPResponse(200); $body = array('__URI__' => $this->uri, 'commands' => $commands, 'cursor' => end(array_keys(self::$_commands)) + 1 ); $response->set_body($body, true);
$this->write($response); $this->_cursor = (int) end(array_keys(self::$_commands)) + 1;}?>
WaterSpout vs Apache/Nginx Neither Apache nor Nginx has a way for one
process to notify the listeners– You need to put the incoming update into
some shared location (mysql, memcache, etc)
– Listeners have to poll the shared location • Heavy• Slows down timeliness
WaterSpout solves this by letting updates notify listeners
WebSockets
Resources– http://dev.w3.org/html5/websockets/– http://en.wikipedia.org/wiki/Web_Sockets– http://www.spoutserver.com/
Questions? Contact
– [email protected] Twitter
– @spoutserver Feedback
– http://joind.in/talk/view/1748 Slides
– http://www.slideshare.net/spoutserver