Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Asynchroniczny PHP & komunikacja czasu rzeczywistego z wykorzystaniem websocketów

1,532 views

Published on

autor: Łukasz Adamczewski, Starszy Programista PHP w Polcode
@lukeadamczewski

Prezentacja 'Asynchroniczny PHP & komunikacja czasu rzeczywistego z wykorzystaniem websocketów' została wygłoszona 17 września 2015 roku podczas 'PHPers Łódź #1', pierwszego spotkania programistów PHP w Łodzi. Firma Polcode miała przyjemność być jednym ze sponsorów tego wydarzenia.

Published in: Technology
  • Be the first to comment

Asynchroniczny PHP & komunikacja czasu rzeczywistego z wykorzystaniem websocketów

  1. 1. Asynchroniczny PHP & komunikacja czasu rzeczywistego z wykorzystaniem websocketów
  2. 2. who am i Nazywam się Łukasz Adamczewski Pracuje w firmie Polcode jako Starszy Programista PHP Szukaj mnie na @lukeadamczewski
  3. 3. - czyli wasz język tego nie potrafi
  4. 4. CPU IO CPU czas IO Model typowej aplikacji
  5. 5. W czym problem? Model typowej aplikacji CPU IO CPU czas IO
  6. 6. asynchroniczna instrukcja CPU Model non-blocking IO IO IO CPU IO CPU CZAS różnyczaswykonywania IO oczekiwanie na koniec poprzedniego zadania
  7. 7. Słów kilka o czasach dostępu czyli IO vs CPU http://norvig.com/21-days.html#answers fetch from L1 cache memory 0.5 nanosec fetch from L2 cache memory 7 nanosec fetch from main memory 100 nanosec send 2K bytes over 1Gbps network 20,000 nanosec read 1MB sequentially from memory 0,25 ms = 250,000 ns Read 1 MB sequentially from SSD* 1 ms Disk seek 10 ms Read 1 MB sequentially from disk 20 ms send packet US to Europe and back 150 ms * Assuming ~1GB/sec SSD
  8. 8. getUser(432, function (user) { console.log(user.name); }); console.log('Done'); Typowy node.js
  9. 9. getUser(432, function (user) { console.log(user.name); }); console.log('Done');1 Typowy node.js
  10. 10. getUser(432, function (user) { console.log(user.name); }); console.log('Done');1 2 Typowy node.js
  11. 11. Node.JS - webserver “hello world” var http = require('http'); var server = new http.Server(); server.on('request', function (req, res) { res.writeHead( 200, {'Content-Type':'text/plain'} ); res.end('Hello World'); }); server.listen(8000, '127.0.0.1');
  12. 12. Node.JS - webserver “hello world” var http = require('http'); var server = new http.Server(); server.on('request', function (req, res) { res.writeHead( 200, {'Content-Type':'text/plain'} ); res.end('Hello World'); }); server.listen(8000, '127.0.0.1'); oczekiwanie na zdarzenie request kod obsługi zdarzenia wysyłający odpowiednie nagłówki do klienta HTTP oraz wysyłający odpowiednią wiadomość
  13. 13. Node.JS - webserver “hello world” var http = require('http'); var server = new http.Server(); server.on('request', function (req, res) { res.writeHead( 200, {'Content-Type':'text/plain'} ); res.end('Hello World'); }); server.listen(8000, '127.0.0.1'); oczekiwanie na zdarzenie request kod obsługi zdarzenia wysyłający odpowiednie nagłówki do klienta HTTP oraz wysyłający odpowiednią wiadomość C10K problem 10k połączeń na jednym rdzeniu
  14. 14. Node.JS - webserver “hello world” var http = require('http'); var server = new http.Server(); server.on('request', function (req, res) { res.writeHead( 200, {'Content-Type':'text/plain'} ); res.end('Hello World'); }); server.listen(8000, '127.0.0.1'); oczekiwanie na zdarzenie request kod obsługi zdarzenia wysyłający odpowiednie nagłówki do klienta HTTP oraz wysyłający odpowiednią wiadomość C10K problem 10k połączeń na jednym rdzeniu
  15. 15. Zróbmy to w PHP!bo w czym problem?
  16. 16. <?php $server = stream_socket_server('tcp://127.0.0.1:8000'); while ($conn = stream_socket_accept($server, -1)) { fwrite($conn, "HTTP/1.1 200 OKrn"); fwrite($conn, "Content-Length: 3rnrn"); fwrite($conn, "Hi PHPersn"); fclose($conn); } $ curl 127.0.0.1:8000 -v > GET / HTTP/1.1 > Host: 127.0.0.1:8000 < HTTP/1.1 200 OK Hi PHPers
  17. 17. <?php $server = stream_socket_server('tcp://127.0.0.1:8000'); while ($conn = stream_socket_accept($server, -1)) { fwrite($conn, "HTTP/1.1 200 OKrn"); fwrite($conn, "Content-Length: 3rnrn"); fwrite($conn, "Hi PHPersn"); fclose($conn); } BLOCKING IO
  18. 18. <?php $server = stream_socket_server('tcp://127.0.0.1:8000'); while ($conn = stream_socket_accept($server, -1)) { fwrite($conn, "HTTP/1.1 200 OKrn"); fwrite($conn, "Content-Length: 3rnrn"); fwrite($conn, "Hi PHPersn"); fclose($conn); } BLOCKING IO NON-BLOCKING IO // If mode is 0, the given stream will be switched to non-blocking mode bool stream_set_blocking ( resource $stream , int $mode ) int stream_select ( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] )
  19. 19. $readable = $read ?: null; $writable = $write ?: null; $except = null; if (stream_select($readable, $writable, $except, 1)) { if ($readable) { foreach ($readable as $stream) { /* code */ } } if ($writable) { foreach ($writable as $stream) { /* code */ } } } Pooling IO
  20. 20. tablica streamów do odczytu tablica streamów do zapisu streamy “faworyzowane” Pooling IOPooling IO $readable = $read ?: null; $writable = $write ?: null; $except = null; if (stream_select($readable, $writable, $except, 1)) { if ($readable) { foreach ($readable as $stream) { /* code */ } } if ($writable) { foreach ($writable as $stream) { /* code */ } } }
  21. 21. EVENT LOOPCZYLI MOŻNA LEPIEJ
  22. 22. EVENT LOOP ➜ Zarządzanie streamami ➜ Ustawianie timerów jednorazowych i cyklicznych ➜ Ustawianie nextTicków i futureTicków ➜ Maksymalna czas zwłoki (timeout) może być zdefiniowany ➜ Jeżeli nie ma dalszych ticków, timerów lub streamów do obsłużenia - event loop kończy pracę
  23. 23. Event Loop: Istnieje kilka implementacji z docelowymi backendami: ● StreamSelectLoop - stream_select ● LibEventLoop - libevent pecl extension ● LibEvLoop - libev pecl extension (najszybszy) ● może kiedyś LibUV :)
  24. 24. Demultiplexer: oczekuje na eventy dla zasobów np. nowe połączenie i powiadamia dispatcher, po czym przechodzi do dalszego nasłuchiwania.
  25. 25. Dispatcher: Komponent służący do rejestracji obsługi zdarzeń. Odbiera synchronicznie event z demultiplexera i wybiera właściwy event handler, który następnie uruchamia.
  26. 26. Obsługa zdarzeń: Event Handler który obsługuje przekierowane do niego zdarzenie. Jest to po prostu jeden ze zdefiniowanych wcześniej callbacków.
  27. 27. Zasoby: Czyli tutaj mamy wszystkie streamy które chcemy obsługiwać. Mogą to być także procesy czy np. uchwyty do plików.
  28. 28. Hello World Again
  29. 29. Instalacja z wykorzystaniem composera curl -sS https://getcomposer.org/installer | php php composer.phar require react/react
  30. 30. require 'vendor/autoload.php'; $loop = ReactEventLoopFactory::create(); $socket = new ReactSocketServer($loop); $http = new ReactHttpServer($socket, $loop); $http->on('request', function ($request, $response) { $response->writeHead(200, ['Content-Type' => 'text/plain']); $response->end("Hi Phpersn"); }); $socket->listen(8000); $loop->run();
  31. 31. Ekosystem EVENT LOOP STREAM TICKS SOCKET HTTP TIMER CHILD PROCESSFILESYSTEM PROMISES
  32. 32. EVENT LOOP STREAM TICKS SOCKET HTTP TIMER CHILD PROCESSFILESYSTEM PROMISES Ticki umożliwiają wykonywanie określonych funkcji w ramach Event Loopa. Dzielą się na $loop- >nextTick($callback) i $loop->futureTick($callback) . Pierwszy jest wykonywany zawsze na początku każdej iteracji loopa, wykonywanie drugiego jest zawsze oddelegowane jako ostatnia operacja w ramach iteracji. Hint: zakolejkowane futureTicki nie będą wykonywane jeśli Event Loop nie ma więcej zadań. NextTicki uzupełniają Event Loop nowymi zadaniami.
  33. 33. EVENT LOOP STREAM TICKS SOCKET HTTP TIMER CHILD PROCESSFILESYSTEM PROMISES Funkcja odmierzania czasu wykonywana w ramach Event Loop w kolejności zaraz po nextTicku. ● $loop->addTimer- jednorazowe wykonanie funkcji po upływie czasu (jak setTimeout) ● $loop->addPeriodicTimer- wykonuje funkcje cyklicznie (jak setInterval) ● $loop->cancelTimer - zatrzymje timer ● $loop->isTimerActive- sprawdza stan działania timera Hint: nie polegaj na czasie odmierzanym przez timery w 100% ponieważ operacje przetwarzane w Event Loop mogą zablokować je na jakiś czas wynikający z bieżących działań.
  34. 34. STREAM SOCKET HTTP CHILD PROCESSFILESYSTEM PROMISES ● Opakowuje natywny zasób stream. ● Rejestrowane w ramach Event Loop’a. ● Stream do odczytu i zapisu (ReadableStream / WriteableStream) ● Potkowość (ang. pipeline). WriteableStream może być ze sobą łączone, więc wyjście jednego jest wejściem drugiego. ● Dane wczytywane / zapisywane do streamów są buforowane Klasy Funkcjonalne: CompositeStream- łączy streamy do odczytu i zapisu łącząc obydwie funkcjonalności ThroughStream- umożliwia modyfikacje danch które stream zawiera - filtrowanie. BufferedSink - konwertuje WriteableStreamdo Promise STREAM
  35. 35. STREAM SOCKET HTTP CHILD PROCESSFILESYSTEM PROMISES Dla funkcji asynchronicznych umożliwia natychmiastowy zwrot wartości, a raczej pewnej zaliczki tej wartości. Przykład - zamiana hosta na ip: $factory = new ReactDnsResolverFactory(); $dns = $factory->create('8.8.8.8', $loop); $dns->resolve('igor.io')->then(function ($ip) { echo "Host: $ipn"; }); PROMISE
  36. 36. STREAM SOCKET HTTP CHILD PROCESSFILESYSTEM PROMISES Komponent sieciowy umożliwiający tworzenie serwerów nasłuchujących nowych połączeń oraz przetwarzających dane połączonych klientów. $socket = new ReactSocketServer($loop); $socket->on('connection', function ($conn) { $conn->on('data', function ($data, $conn) { $conn->write($data); }); }); $socket->listen(1337); SOCKET
  37. 37. Websockety Komunikacja w czasie rzeczywistym
  38. 38. Websockety - zalety ➜ Wsparcie we wszystkich wiodących przeglądarkach (> IE8) ➜ Dwukierunkowość komunikacji ➜ Niezależnie wysyłanie wiadomości ➜ Protokół oparty na HTTP ➜ Niewielki rozmiar pojedynczego pakietu danych
  39. 39. WebsocketAPI - interfejs kliencki JavaScript var websocket = new WebSocket('ws://localhost:8000' ); websocket.onopen = function(evt) {}; websocket.onclose = function(evt) {}; websocket.onmessage = function(evt) {}; websocket.onerror = function(evt) {};
  40. 40. Ratchet - WebSockets for PHP
  41. 41. class WS implements MessageComponentInterface { function onOpen(ConnectionInterface $conn) {} function onClose(ConnectionInterface $conn) {} function onError(ConnectionInterface $conn, Exception $e) {} function onMessage(ConnectionInterface $from, $msg) { $from->send($msg); } } $server = IoServer::factory( new HttpServer(new WsServer(new WS())), 3000 ); $server->run();
  42. 42. The Web Application Messaging Protocol
  43. 43. The Bigger The Better ➜ Remote Procedure Calls (RPC) ➜ Publish & Subscribe ➜ Autobahn.JS ➜ integracja ZeroMQ ➜ integracja Redis
  44. 44. Autobahn.JS? // dla wersji AUTOBAHNJS_VERSION="0.7.1" var session = new ab.Session( "ws://127.0.0.1:3000", function () { // zostaliśmy połączeni }, function () { // zostaliśmy rozłączeni }, { 'skipSubprotocolCheck': true, 'maxRetries': 5, 'retryDelay': 2000 } ); session.subscribe("http://phpers.pl/event/message", callback);
  45. 45. class WAMP implements RatchetWampWampServerInterface { public function onPublish(ConnectionInterface $conn, $topic, $event, array $exclude, array $eligible) {} public function onCall(ConnectionInterface $conn, $id, $topic, array $params) {} public function onSubscribe(ConnectionInterface $conn, $topic) {} public function onUnSubscribe(ConnectionInterface $conn, $topic) {} public function onOpen(ConnectionInterface $conn) {} public function onClose(ConnectionInterface $conn) {} public function onError(ConnectionInterface $conn, Exception $e) {} } $server = RatchetServerIoServer::factory( new HttpServer(new WsServer(new WampServer(new WAMP()))), 3000 ); $server->run(); Ratchet + WAMP
  46. 46. $loop = ReactEventLoopFactory::create(); $pusher = new WAMP; $context = new ReactZMQContext($loop); $pull = $context->getSocket(ZMQ::SOCKET_PULL); $pull->bind('tcp://127.0.0.1:5555'); $pull->on('message', array($pusher, 'onQueueAdded')); $socket = new ReactSocketServer($loop); $socket->listen(3000, '0.0.0.0'); $webServer = new IoServer( new HttpServer(new WsServer(new WampServer($pusher))), $socket ); $loop->run(); ZeroMQ + Ratchet
  47. 47. ZeroMQ + Ratchet $loop = ReactEventLoopFactory::create(); $pusher = new WAMP; $context = new ReactZMQContext($loop); $pull = $context->getSocket(ZMQ::SOCKET_PULL); $pull->bind('tcp://127.0.0.1:5555'); $pull->on('message', array($pusher, 'onQueueAdded')); $socket = new ReactSocketServer($loop); $socket->listen(3000, '0.0.0.0'); $webServer = new IoServer( new HttpServer(new WsServer(new WampServer($pusher))), $socket ); $loop->run(); $context = new ZMQContext(); $socket = $context->getSocket( ZMQ::SOCKET_PUSH, 'websocket' ); $socket->connect('tcp://127.0.0.1:5555'); $socket->send($payloadAsJSON);
  48. 48. ZeroMQ + Ratchet $loop = ReactEventLoopFactory::create(); $pusher = new WAMP; $context = new ReactZMQContext($loop); $pull = $context->getSocket(ZMQ::SOCKET_PULL); $pull->bind('tcp://127.0.0.1:5555'); $pull->on('message', array($pusher, 'onQueueAdded')); $socket = new ReactSocketServer($loop); $socket->listen(3000, '0.0.0.0'); $webServer = new IoServer( new HttpServer(new WsServer(new WampServer($pusher))), $socket ); $loop->run(); $context = new ZMQContext(); $socket = $context->getSocket( ZMQ::SOCKET_PUSH, 'websocket' ); $socket->connect('tcp://127.0.0.1:5555'); $socket->send($payloadAsJSON); public function onQueueAdded($payload) { $payloadData = json_decode($payload, true); // dalsze przetwarzanie }
  49. 49. MAO? ➜ WAMP2 ➜ voryx/Thruway (kompatybilny z nowym Autobahn.JS) ➜ bixuehujin/reactphp-mysql ➜ DNode ➜ STOMP ➜ AR.Drone ➜ Whois ➜ Childprocess
  50. 50. Koniec! Macie pytania?
  51. 51. Odniesienia i inspiracje ➜ http://blog.wyrihaximus.net/categories/reactphp/ ➜ Presentation theme - SlidesCarnival ➜ Zdjęcia - Death to the Stock Photo ➜ http://www.slideshare. net/SteveRhoades2/asynchronous-php-and-realtime- messaging ➜ https://speakerdeck.com/jmikola/async-php-with-react ➜ https://speakerdeck.com/igorw/react-phpnw

×