using web sockets in your php application - truedeveloper.co · including sockets • php has a...
TRANSCRIPT
Aboutme
• Buildingwebsitessince1999,workingwithPHPsince2001
• Officemanager(cat)doesn’tgetenoughscratches
Whatarewebsockets?
• PartofHTML5• Full-duplexclient/servercommunication
• Forrealtimecommunicationbetweenclientandserver
• accessiblefromnon-browserapplications
• Builtinto(most)browsers.
WebsocketsVSAJAX • Websocketsarefull-duplex
• Client-serverrelationship• Connectionispersistentandstateful
• Authenticationisbuilt-intoAJAX
WAMP• No,notthestack• WebApplicationMessagingProtocol
• Facilitatestwotypesofmessagingpatterns:publishandsubscribe(pub/sub)and,remoteproceduralcall(RPC)
WAMP
• Aclientsubscribestoatopicandreceivesallmessagesinthetopic
• Notlimitedto1:1communication.
• Scalesfrom0toNclients• Clientcannotrequestinformationoutsideofwhatispushed
• Canbeusedasachat,updatingconfigurationbetweenservices
Pub/Sub
WAMP• Cancallcustomfunctions• 1:1communicationwiththeserver
• Canbeusedtorequestdata,directcommunicationbetweenbrowsers,games
• Servercanmakearequestofthebrowserandviceversa
RPC
Ratchet• PHPimplementationofWAMPprotocol
• Providesserversidehooksforpub/subaswellasRPC
• Providesaflashbasedclientsidepolyfill
• Providesaread-onlySymfonysessionhandler
• Andmore!• Canadian!
Thenewshiny• RatchetdoesnotsupportWAMP2
• Thruwaydoes(https://github.com/voryx/Thruway)
WAMPServerServer:
<?php use Ratchet\Server\IoServer; use Ratchet\Http\HttpServer; use Ratchet\WebSocket\WsServer; use Ratchet\Wamp\WampServer; use Ratchet\Cookbook\OpenPubSub;
require dirname(__DIR__) . '/vendor/autoload.php';
use MyApp\WampDemo;
$server = IoServer::factory( new HttpServer( new WsServer( new WampServer( new WampDemo ) ) ) , 8080
); $server->run();
class WampDemo implements WampServerInterface { protected $clients;
public function __construct() {
$this->clients = new \SplObjectStorage; }
public function onCall(ConnectionInterface $conn, $id, $topic, array $params) {
echo "Calling $topic"; }
public function onSubscribe(ConnectionInterface $conn, $topic) { echo "Subscribing to $topic";
}
public function onUnSubscribe(ConnectionInterface $conn, $topic) { echo "Unsubscribing to $topic";
}
public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible) { echo "Publishing to $topic";
}
public function onOpen(ConnectionInterface $conn) { $this->clients->attach($conn); echo "Connected\n";
}
public function onClose(ConnectionInterface $conn) { $this->clients->detach($conn);
echo "Closed\n"; }
public function onError(ConnectionInterface $conn, \Exception $e) { echo "An error has occurred: {$e->getMessage()}\n";
$conn->close(); }
}
Clientside<script>
var connection = new autobahn.Connection({
url: “ws://127.0.0.1:8080”,
realm: "demo"
});
connection.onopen = function (session, details) {
console.log("Connection established!", session, details);
session.call(“test");
session.subscribe("test.topic");
session.publish("test.topic");
};
connection.onclose = function (reason, details) {
console.log("Connection lost: " + reason);
}
connection.onmessage = function(e) {
console.log(e.data);
};
connection.open();
</script>
Howweusedwebsockets
• Applicationbuiltbyexternalvendor
• eachAJAXrequestveryslow-1000ms+
• Dataintegritywaspoor:APP.Case.Score=100;
• Duplicationoffunctionalitybetweenclientandserverside
• Gamewasattachedtoalargelegacyapplication
Theproblem
Howweusedwebsockets
• Movelogicsomewherethatcan’tbeaccessedbyauser
• Speedapplicationup• Needstobeextensible• Couldnotbeentirelyrewritten
• Thebusinesswantsitdoneyesterday
Whatweneededtodo
Howweusedwebsockets
• WebsocketsarefasterthanAJAX
• Websocketsarepersistent• Scoringlogicnowserverside
• Gameisnoweventdriven• Lostdataislesslikely
Thesolution
Notsofast…• PHPisnotgreatatrunning24/7
• CrashyMcCrashface• Firewalls&Proxies
akaThesearethepitfallsI’veknown
CachingCrashes• Getourhandsdirtywithcoredumps
• Crashingwhencallingredit_sock_write
• Connectiontocachingserverwasgettingdisconnected
ulimitgottabekiddingme
PHP Fatal error: Uncaught exception ... Too many open files
• EverythinginUNIXisafile-includingsockets
• PHPhasadefaultlimitof1024openfiles
jeff@devlocal:~$ ulimit -n 65535
ulimitgottabekiddingme
• OSgavePHPafilehandlethatwashigherthanexpected,socrashed
• Atheoreticallimitof1024connectionsperserver(probablylower)
• lowernumberofopenfiles• Createnewuserandedit/etc/security/limits.conf
websocket-user hard nofile 1024 websocket-user soft nofile 1024
Problembyproxy
• Morerestrictivefirewallsmayneedtowhitelistwebsocketservers
• Usefulfordiagnosis:http://www.websocket.org/echo.html
• Matchprotocolofwebpageandwebsocket(IE)
• Runwsstopassthroughproxyservers
• Manyfirewallsblockportsabove1024
Balancingdaemons
• Useupstarttorunwebsocketserver
• Restartdaily• Buildinareconnecttohelpautomaticallyreconnectusers
Abalancingact• Rackspaceloadbalancersdon’tsupportwebsockets
• Amazon’sELBdoes(withsomework)
• HAProxydoes• ApacheandNginxsupportloadbalancingandroutingthroughaURL
Nginx
• WeloadbalancethroughNginx(willswitchtoHAProxy)
• AbletoredirectwebsocketstoaURL
• Routedthroughport443
Nginx/websocket/
(port 80)
Websocket server
(port 8000)
Websocket server
(port 8000)
Websocket server
(port 8000)
Nginxconfiguration
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream websocket {
least_conn;
server 192.168.100.10:8000;
server 192.168.100.11:8000;
server 192.168.100.12:8000;
}
server {
listen 8000;
location /websocket/ {
proxy_pass http://websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
Monitoring• Loadbalancinghelps,butrequiresanumberoffailuresbeforedown
• Addinganewpieceofinfrastructure-monitorit!
• Pingdomdon’tsupportwebsocketmonitoring
• Newrelicsupportsprocessmonitoring
Monitoringprivate function pingServer($server, $port, $path) { $loop = React\EventLoop\Factory::create(); App::import('Lib', 'Simucase.Client');
$client = new Client($loop, $server, $port, $path); $response = null;
$params = [json_encode([])];
$client->setOnWelcomeCallback(function (Client $conn, $data) use (&$response, $loop, $params) { $conn->call('helloWorld', $params, function ($data) use (&$response, $loop, $conn) { $response = $data; $loop->stop(); }); });
$loop->run(); return $response; }
public function pingServers() {
$reports = [];
foreach ($this->servers as $server) {
try {
$report = $this->pingServer($server, $this->port, $this->path);
}
catch (Exception $e) {
$report = "$server is down";
$value = ['server' => $server];
AppStat::increment('webapp.server.down', $value);
}
$reports[$server] = $report;
}
return $reports;
}
InSummary• FasterandmorerobustthanAJAX
• ThinklongandhardbeforeusingPHP
• Addingapieceoffussyinfrastructure
• Knowyouraudience• Informyouraudience
Thankyou!Questions?Slidesavailableat
http://truedeveloper.co/blog/websockets/
Pleaseratemytalk:
https://legacy.joind.in/19720
Samplecodeavailableathttps://github.com/jeffkolez/ratchet-websocket-demo
I’montwitter:@jkolezEmail:[email protected]