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.
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
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. 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. 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. 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
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.
24.
25. Event Loop:
Istnieje kilka implementacji z docelowymi backendami:
● StreamSelectLoop - stream_select
● LibEventLoop - libevent pecl extension
● LibEvLoop - libev pecl extension (najszybszy)
● może kiedyś LibUV :)
26. Demultiplexer:
oczekuje na eventy dla zasobów np.
nowe połączenie i powiadamia
dispatcher, po czym przechodzi do
dalszego nasłuchiwania.
27. 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.
28. Obsługa zdarzeń:
Event Handler który obsługuje
przekierowane do niego zdarzenie. Jest
to po prostu jeden ze zdefiniowanych
wcześniej callbacków.
29. Zasoby:
Czyli tutaj mamy wszystkie streamy które
chcemy obsługiwać. Mogą to być także
procesy czy np. uchwyty do plików.
34. 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.
35. 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ń.
36. 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
37. 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
38. 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
40. 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
46. 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);
47. 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
48. $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
49. 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);
50. 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
}