http://dev.r3c.com.br:1234

Palestra interativa!

Smartphone / Tablet / Notebook

Aumente o volume!

async e websockets com PHP

Muito prazer, pode me chamar de Bob!

  • Evangelista PHP - VIVA PHP SP CAMPINAS! VIVA PHP!
  • Já fiz mais de 100 sites! \o/
  • Trabalho na Memed, fazendo uma plataforma de prescrição médica
  • Sou fução desde quando nasci...

Gosto do PHP porque

Nós somos piratas!

Num belo dia, precisei fazer algo assim:

Buscando usuários:


//Vou querer X usuários que encaixam nessa busca,
//batata grande e uma coca-cola
$usuarios = Usuarios::where('nome', $q)->get();

//Processa a resposta
$usuarios = $this->processarResposta($usuarios);

//Vai que é tua View!
return view('resultado', ['usuarios' => $usuarios]);
					

Você faz isso?

Sim (0)
Não (0)

$usuarios = Usuarios::where('nome', $q)->get();

//Só será executado daqui para frente após
//a resposta do banco de dados (IO) chegar

$usuarios = $this->processarResposta($usuarios);

//Aqui após um processo intensivo de CPU

return view('resultado', ['usuarios' => $usuarios]);
					

Blocking IO

Preciso ser mais rápido!

Vou processar metade dos usuários em paralelo com a outra metade

Node.js


Usuarios.find({
	where: {nome: q},
	limit: 5000
}).then(function(usuarios) {
	//Só executa aqui quando chegar o resultado
	processarResultado(usuarios);
});

Usuarios.find({
	where: {nome: q},
	offset: 5000,
	limit: 5000
}).then(function(usuarios) {
	//Só executa aqui quando chegar o resultado
	processarResultado(usuarios);
});
					

E no PHP?

Você sabe como resolver isso?

Sim (0)
Não (0)

Threading

FORK

O que um fork faz?

Cria novo processo, do zero! (0)
Cria um clone e copia a memória (0)
Cria um clone e usa a mesma memória (0)

$pid = pcntl_fork();

if ($pid == -1) {
     die('could not fork =(');
} else if ($pid) {
    // I'm your father!

    \DB::reconnect('mysql');
    $users = $this->getUsers($totalUsers / 2, 0);

    //Aguarda o processo filho terminar
    pcntl_wait($status);

    //Faz um merge do que o processo pai e filho processaram
    //Os dados do processo filho estão no cache
    $users = array_merge($users, \Cache::pull($cacheKey));
}
					

} else {
    // we are the child
    \DB::reconnect('mysql');

    $users = $this->getUsers($totalUsers / 2, $totalUsers / 2);

    //Armazena os usuários processados no cache
    \Cache::forever($cacheKey, $users);

    die();
}
					

Buscando 10.000 usuários (com ORM)

Forma tradicional: 3063,82ms

Fork: 2029,68ms

O Fork tem um problema:

Não funciona quando o PHP é executado através do Apache e do NGINX

Non blocking IO


//cria o main loop
$loop = \React\EventLoop\Factory::create();

//cria a conexão com o MySQL
$connection = new \React\MySQL\Connection($loop, array(
    'dbname' => $_ENV['DB_DATABASE'],
    'user'   => $_ENV['DB_USERNAME'],
    'passwd' => $_ENV['DB_PASSWORD'],
));

$connection->connect(function () {});
					

$connection->query($query, function ($command, $conn) use ($loop) {
    if ($command->hasError()) {
        $error = $command->getError();
    } else {
        $results = $command->resultRows;
        $this->users = array_merge($this->users, $this->processUsers($results));
        $this->queries--;

        if ($this->queries == 0) {
            $loop->stop();
        }
    }
});
					

Quem ganhou?

Forma tradicional (0)
React (0)

Buscando 10.000 usuários (sem ORM)

Forma tradicional: 659,02ms

React MySQL: 1123,89ms

Por que com REACT não foi melhor?

IO - CPU - CPU

react/mysql v0.2.0

Calma, o REACT nos deu super poderes!

Exemplo de servidor


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

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

$socket->on('connection', function ($conn) use ($totalUsers) {
    echo 'Enviando mensagem...' . PHP_EOL;
    $users = $this->getUsers($totalUsers / 2, $totalUsers / 2);
    $conn->end(serialize($users));
    echo 'Enviada' . PHP_EOL;
});

$socket->listen(1337);

$loop->run();
					

Exemplo de cliente


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

$dnsResolverFactory = new \React\Dns\Resolver\Factory();
$dns = $dnsResolverFactory->createCached('8.8.8.8', $loop);

$connector = new \React\SocketClient\Connector($loop, $dns);

$connector->create('127.0.0.1', 1337)->then(function (\React\Stream\Stream $stream) use (&$buffer) {
    $stream->on('data', function ($data, $stream) use (&$buffer) {
        $buffer .= $data;
    });
});

$loop->run();
					

Quem ganhou?

Forma tradicional (0)
React (0)

Buscando 10.000 usuários (com ORM)

Forma tradicional: 3063,82ms

Fork: 2029,68ms

REACT Socket: 1910ms

Eu quero mais!

RFC 6455 - Dezembro de 2011

  • Em 12 de Setembro de 2011 o Google Chrome 15 já implementava
  • The WebSocket Protocol enables two-way communication between a client running untrusted code in a controlled environment to a remote host that has opted-in to communications from that code.

Já usou Websockets?

Sim (0)
Não (0)

Vantagem de usar WebSockets:

  • O servidor websocket pode ficar na mesma porta que o HTTP padrão, ele usa campos do cabeçalho HTTP distintos
  • Suporta um servidor rodando múltiplos domínios
  • Suporta proxies HTTP
  • Só envia cabeçalhos HTTP no ato da conexão, depois é dado puro (em UTF-8 ou binário)

Socket.io

Biblioteca para NodeJS de servidor de WebSockets

Funciona bem, mas não é mais Websocket puro, vai muito além!

Servidor


$controller = new Controller();

$server = IoServer::factory(
    new HttpServer(
        new WsServer(
            $controller
        )
    ),
    777
);

$loop = $server->loop;

$loop->addPeriodicTimer(5, function () use ($controller) {
    $controller->sendCounterMessage();
});

$server->run();
                    

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class App implements MessageComponentInterface
{
    public static $connections;
    public static $lastMessage;

    public function __construct()
    {
        self::$connections = new \SplObjectStorage;
    }
                    

public function onOpen(ConnectionInterface $connection)
{
    self::$connections->attach($connection);

    if (isset(App::$lastMessage)) {
        Sender::send(App::$lastMessage, $connection);
    }

    Log::d('Espectador conectado: ' . $connection->resourceId);
}
                    


public function onMessage(ConnectionInterface $connection, $message)
{
    App::$lastMessage = $message;

    foreach (self::$connections as $anotherConnection) {
        if ($anotherConnection !== $connection) {
            Sender::send($message, $anotherConnection);
        }
    }

    Log::d($message);
}
                    

Padronize seus objetos


class HornMessage extends Message
{
    protected $type = 'horn';

    //getters and setters
}

class SlideMessage extends Message
{
    protected $type = 'slide';
    protected $indexh;
    protected $indexv;
    protected $indexf;

    //getters and setters
}
                    

Escalabilidade

Seu servidor WebSocket somente cuida dos WebSockets

Tchau pessoal, até a próxima!

Avalie minha palestra! CLIQUE AQUI

@gabrielrcouto

PHPSP

PHPSP Campinas

Essa palestra está no GitHub

0 / 0

Buzinar