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/