asynchroniczny php i komunikacja czasu rzeczywistego z wykorzystaniem websocketw

53
Asynchroniczny PHP & komunikacja czasu rzeczywistego z wykorzystaniem websocketów

Upload: luke-adamczewski

Post on 16-Apr-2017

627 views

Category:

Software


1 download

TRANSCRIPT

Asynchroniczny PHP& komunikacja czasu

rzeczywistego z wykorzystaniemwebsocketów

who am iNazywam się Łukasz AdamczewskiPracuje w firmie Polcode jako Starszy Programista PHP

Szukaj mnie na @lukeadamczewski

- czyli wasz język tego nie potrafi

CPU IO CPU

czas

IO

Model typowej aplikacji

W czym problem?

Model typowej aplikacji

CPU IO CPU

czas

IO

asynchroniczna instrukcja

CPU

Model non-blocking IO

IO

IO

CPU

IO

CPU

CZA

S

różny czas wykonyw

ania

IO

CPU

oczekiwanie na koniec poprzedniego zadania

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

getUser(432, function (user) { console.log(user.name);}); console.log('Done');

Typowy node.js

getUser(432, function (user) { console.log(user.name);}); console.log('Done');1

Typowy node.js

getUser(432, function (user) { console.log(user.name);}); console.log('Done');1

2

Typowy node.js

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');

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ść

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

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

Zróbmy to w PHP!

bo w czym problem?

<?php

$server = stream_socket_server('tcp://127.0.0.1:8000');

while ($conn = stream_socket_accept($server, -1)) {

fwrite($conn, "HTTP/1.1 200 OK\r\n");

fwrite($conn, "Content-Length: 3\r\n\r\n");

fwrite($conn, "Hi PHPers\n");

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

<?php

$server = stream_socket_server('tcp://127.0.0.1:8000');

while ($conn = stream_socket_accept($server, -1)) {

fwrite($conn, "HTTP/1.1 200 OK\r\n");

fwrite($conn, "Content-Length: 3\r\n\r\n");

fwrite($conn, "Hi PHPers\n");

fclose($conn);

}

BLOCKING

IO

<?php

$server = stream_socket_server('tcp://127.0.0.1:8000');

while ($conn = stream_socket_accept($server, -1)) {

fwrite($conn, "HTTP/1.1 200 OK\r\n");

fwrite($conn, "Content-Length: 3\r\n\r\n");

fwrite($conn, "Hi PHPers\n");

fclose($conn);

}

BLOCKING

IO

NON-BLOCKING

IO// If mode is 0, the given stream will be switched to non-blocking modebool stream_set_blocking ( resource $stream , int $mode )

int stream_select ( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] )

$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

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 */ }

}

}

EVENT LOOP

CZYLI MOŻNA LEPIEJ

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ę

Event Loop:Istnieje kilka implementacji z docelowymi backendami:

● StreamSelectLoop - stream_select● LibEventLoop - libevent pecl extension● LibEvLoop - libev pecl extension (najszybszy)● może kiedyś LibUV :)

Demultiplexer:oczekuje na eventy dla zasobów np. nowe połączenie i powiadamia dispatcher, po czym przechodzi do dalszego nasłuchiwania.

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.

Obsługa zdarzeń:Event Handler który obsługuje przekierowane do niego zdarzenie. Jest to po prostu jeden ze zdefiniowanych wcześniej callbacków.

Zasoby:Czyli tutaj mamy wszystkie streamy które chcemy obsługiwać. Mogą to być także procesy czy np. uchwyty do plików.

Hello World Again

Instalacja z wykorzystaniem composera

curl -sS https://getcomposer.org/installer | php

php composer.phar require react/react

require 'vendor/autoload.php';

$loop = React\EventLoop\Factory::create();

$socket = new React\Socket\Server($loop);

$http = new React\Http\Server($socket, $loop);

$http->on('request', function ($request, $response) {

$response->writeHead(200, ['Content-Type' =>

'text/plain']);

$response->end("Hi Phpers\n");

});

$socket->listen(8000);

$loop->run();

Ekosystem

EVENT LOOP

STREAM

TICKS

SOCKET

HTTP

TIMER

CHILD PROCESSFILESYSTEM

PROMISES

EVENT LOOP

STREAM

TICKS

SOCKET

HTTP

TIMER

CHILD PROCESSFILESYSTEM

PROMISES

Tickiumoż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.

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ń.

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ściThroughStream - umożliwia modyfikacje danch które stream zawiera - filtrowanie. BufferedSink - konwertuje WriteableStream do Promise

STREAM

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 React\Dns\Resolver\Factory();

$dns = $factory->create('8.8.8.8', $loop);

$dns->resolve('igor.io')->then(function ($ip) {

echo "Host: $ip\n";

});

PROMISE

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 React\Socket\Server($loop);

$socket->on('connection', function ($conn) {

$conn->on('data', function ($data, $conn) {

$conn->write($data);

});

});

$socket->listen(1337);

SOCKET

WebsocketyKomunikacja w czasie rzeczywistym

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

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) {};

Ratchet - WebSockets for PHP

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();

The Web Application Messaging Protocol

The Bigger The Better

➜ Remote Procedure Calls (RPC)➜ Publish & Subscribe➜ Autobahn.JS➜ integracja ZeroMQ➜ integracja Redis

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);

class WAMP implements Ratchet\Wamp\WampServerInterface {

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 = \Ratchet\Server\IoServer::factory(

new HttpServer(new WsServer(new WampServer(new WAMP()))),

3000

);

$server->run();

Ratchet + WAMP

$loop = React\EventLoop\Factory::create();

$pusher = new WAMP;

$context = new React\ZMQ\Context($loop);

$pull = $context->getSocket(ZMQ::SOCKET_PULL);

$pull->bind('tcp://127.0.0.1:5555');

$pull->on('message', array($pusher, 'onQueueAdded'));

$socket = new React\Socket\Server($loop);

$socket->listen(3000, '0.0.0.0');

$webServer = new IoServer(

new HttpServer(new WsServer(new WampServer($pusher))),

$socket

);

$loop->run();

ZeroMQ + Ratchet

ZeroMQ + Ratchet

$loop = React\EventLoop\Factory::create();

$pusher = new WAMP;

$context = new React\ZMQ\Context($loop);

$pull = $context->getSocket(ZMQ::SOCKET_PULL);

$pull->bind('tcp://127.0.0.1:5555');

$pull->on('message', array($pusher, 'onQueueAdded'));

$socket = new React\Socket\Server($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);

ZeroMQ + Ratchet

$loop = React\EventLoop\Factory::create();

$pusher = new WAMP;

$context = new React\ZMQ\Context($loop);

$pull = $context->getSocket(ZMQ::SOCKET_PULL);

$pull->bind('tcp://127.0.0.1:5555');

$pull->on('message', array($pusher, 'onQueueAdded'));

$socket = new React\Socket\Server($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 }

MAO?➜ WAMP2➜ voryx/Thruway (kompatybilny z nowym

Autobahn.JS)

➜ bixuehujin/reactphp-mysql

➜ DNode

➜ STOMP

➜ AR.Drone

➜ Whois

➜ Childprocess

Koniec!Macie pytania?