Eu normalmente jogo muito com os servidores TCP, clientes e com coisas assim no meu trabalho diário. Eu gosto de usar o daemon xinet.d do Linux para lidar com as portas TCP.
Essa abordagem funciona bem. Você não precisa abrir qualquer porta. xinet.d abre para você e invoca os scripts PHP. O problema aparece quando chamamos intensamente o nosso servidor xinet.d. Ele cria uma instância de PHP por pedido. Não é um problema com um pedido, por exemplo, de 3 segundos, mas se temos de lidar com 10 pedidos por segundo, nossa carga do servidor vai crescer. A solução: um servidor dedicado.
Com o PHP, podemos criar servidores dedicados usando, por exemplo, o Ratchet. Eu quero criar uma biblioteca usando Ratchet para abrir as portas TCP e registrar callbacks para essas portas para manipular os pedidos (padrão Reactor). Você conhece Silex? Claro que sim. Essa biblioteca pega emprestada a ideia do Silex (registrar callbacks para rotas) para o mundo do TCP.
Deixe-me mostrar exemplos:
Exemplo 1:
use React\EventLoop\Factory as LoopFactory;
use G\Pxi\Pxinetd;
$loop = LoopFactory::create();
$service = new Pxinetd('0.0.0.0');
$service->on(8080, function ($data) {
echo $data;
});
$service->register($loop);
$loop->run();
Esse é o exemplo mais simples. Um servidor de eco TCP. Nós abrimos a porta 8080 para todas as interfaces (0.0.0.0) e retornamos um simples eco de entrada.
Podemos começar portas diferentes também:
use G\Pxi\Pxinetd;
use React\EventLoop\Factory as LoopFactory;
$loop = LoopFactory::create();
$service = new Pxinetd('0.0.0.0');
$service->on(8080, function ($data) {
echo $data;
});
$service->on(8888, function ($data) {
echo $data;
});
$service->register($loop);
$loop->run();
Exemplo 2:
Nós também podemos trabalhar com a conexão
use G\Pxi\Pxinetd;
use G\Pxi\Connection;
use React\EventLoop\Factory as LoopFactory;
$loop = LoopFactory::create();
$service = new Pxinetd('0.0.0.0');
$service->on(8080, function ($data) {
echo $data;
});
$service->on(8088, function ($data, Connection $conn) {
var_dump($conn->getRemoteAddress());
echo $data;
$conn->send("....");
$conn->close();
});
$service->register($loop);
$loop->run();
Exemplo 3:
Eu sou um grande fã das configurações YAML, para que possamos carregar as configurações de um arquivo YAML, claro:
// Services/Reader1.php
use G\Pxi\Connection;
use G\Pxi\MessageIface;
class Reader1 implements MessageIface
{
public function onMessage($data, Connection $conn)
{
echo $data . $conn->getRemoteAddress();
}
}
use G\Pxi\Pxinetd;
use G\Pxi\YamlFileLoader;
use Symfony\Component\Config\FileLocator;
use React\EventLoop\Factory as LoopFactory;
$loop = LoopFactory::create();
$service = new Pxinetd('0.0.0.0');
$loader = new YamlFileLoader($service, new FileLocator(__DIR__ ));
$loader->load('conf3.yml');
$service->on(8080, function ($data) {
echo "$data";
});
$service->register($loop);
$loop->run();
Exemplo 4:
Estamos usando symfony/config e os componentes symfony/yaml para que a gente possa utilizar de hierarquia dentro de nossos arquivos YAML:
use G\Pxi\Pxinetd;
use G\Pxi\YamlFileLoader;
use Symfony\Component\Config\FileLocator;
use React\EventLoop\Factory as LoopFactory;
$loop = LoopFactory::create();
$service = new Pxinetd('0.0.0.0');
$loader = new YamlFileLoader($service, new FileLocator(__DIR__));
$loader->load('conf4.yml');
$service->on(8080, function ($data) {
echo "$data";
});
$service->register($loop);
$loop->run();
config4.yml:
imports:
- { resource: conf4_2.yml }
ports:
9999:
class: Services\Reader1
config4_2.yml
ports:
7777:
class: Services\Reader1
Exemplo 5:
E, finalmente, um bônus. Este script é de thread única. Isso significa que se um processo demorar muito tempo, ele vai bloquear para o resto dos processos. Podemos implemente threads, mas eu tento evitá-las ao máximo. Prefiro criar um aplicativo Silex (atrás de um servidor http) e executar as solicitações HTTP para “emular” threads de uma forma simples.
use G\Pxi\Pxinetd;
use G\Pxi\YamlFileLoader;
use Symfony\Component\Config\FileLocator;
use React\EventLoop\Factory as LoopFactory;
$loop = LoopFactory::create();
$service = new Pxinetd('0.0.0.0');
$loader = new YamlFileLoader($service, new FileLocator(__DIR__ ));
$loader->load('conf5.yml');
$service->on(8080, function ($data) {
echo "$data";
});
$service->register($loop);
$loop->run();
conf5.yml
ports:
9999:
class: Services\Reader1
9991:
url: http://localhost:8899/onMessage/{data}
9992:
url: http://localhost:8899/simulateError/{data}
E agora o nosso servidor Silex em execução na porta 8899:
include __DIR__ . "/../../vendor/autoload.php";
use Silex\Application;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
$app = new Application();
$app->get('/onMessage/{data}', function ($data) {
return "OK" . "'{$data}'";
});
$app->get('/simulateError/{data}', function ($data) {
throw new NotFoundHttpException();
});
$app->run();
E isso é tudo. O que você acha? Você pode ver toda a biblioteca na minha conta no GitHub.
***
Gonzalo Ayuso faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: http://gonzalo123.com/2015/04/13/building-tcp-server-daemon-with-php-and-rachet/




