Realtime Communication Techniques with PHP

Preview:

Citation preview

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

– support@spoutserver.com Twitter

– @spoutserver Feedback

– http://joind.in/talk/view/1748 Slides

– http://www.slideshare.net/spoutserver

Recommended