ReactPHP: Socket clients
Last week I've covered creating a simple socket server, this week we'll create a client to communicate with it using react/socket-client
.
Installation
Once again installation is simple, just run the following composer command and it will pull in all dependencies:
composer require react/socket-client
We need DNS
Before we can make connections to anything we need a DNS resolver. Technically they aren't needed to lookup IP addresses we'll use in the examples. But the socket client requires it in case you're connecting to a hostname instead of an IP address. Another thing: it can't look up anything in your hosts
file, so localhost
won't resolve. Setting up is simple and we'll be using the cached version so we don't do a DNS query each time we need to look a hostname up. (You might remember it from the promises article.)
<?php
require 'vendor/autoload.php';
$loop = React\EventLoop\Factory::create();
$dnsResolverFactory = new React\Dns\Resolver\Factory();
$dns = $dnsResolverFactory->createCached('8.8.8.8', $loop);
Connecting to the echo server
So lets one again take the counting example from the timers article, mash that up with what we learned in the streaming article. And create a simple socket client that writes a counter into a connection stream to the echo server from the sockets article. Now when you call the create method on the connector it will lookup the target host you give if it isn't an IP. Then it attempts to connect to the servers IP plus given port name.
<?php
require 'vendor/autoload.php';
$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 ($loop) {
$i = 0;
$loop->addPeriodicTimer(1, function(React\EventLoop\Timer\Timer $timer) use (&$i, $loop, $stream) {
$stream->write(++$i . PHP_EOL);
if ($i >= 15) {
$loop->cancelTimer($timer);
$stream->close();
}
});
$stream->on('data', function ($data) {
echo $data;
});
});
$loop->run();
Tic Tac toe
That looked pretty simple right? Lets take the tic tac toe game server from the sockets post and create a client for it. What this does is connect to the game server, echo everything the server sends it back to the CLI and pick a random location everytime the server sends Your turn:
.
<?php
require 'vendor/autoload.php';
$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) {
$buffer = '';
$stream->on('data', function ($data, $stream) use (&$buffer) {
echo $data;
$cols = ['a', 'b', 'c'];
$rows = [1, 2, 3];
$buffer .= $data;
if (strpos($buffer, PHP_EOL) !== false) {
$chunks = explode(PHP_EOL, $buffer);
$buffer = array_pop($chunks);
foreach ($chunks as $chunk) {
if (trim($chunk) == 'Your turn:') {
$stream->write($cols[mt_rand(0 ,2)] . $rows[mt_rand(0 ,2)] . PHP_EOL);
}
}
}
});
});
$loop->run();
Community examples
This weeks community examples umpirsky/wisdom
and clue/docker-react
utilize socket clients for the connection creating to remove servers.
umpirsky/wisdom
umpirsky/wisdom
utilizes react/whois
to check if a domain is available. By using react/whois
it uses the socket client to connect to an external server attemping to fetch information about a domain. It's simple in use as the example from the github readme shows:
<?php
$domain = 'umpirsky.com';
$wisdom = new Wisdom($client);
$wisdom
->check($domain)
->then(function ($available) use ($domain) {
printf('Domain %s is %s.', $domain, $available ? 'available' : 'taken');
});
// Outputs:
// Domain umpirsky.com is taken.
clue/docker-react
clue/docker-react
is an asynchronous Docker API client that lets you controll a docker daemon. The following example fetches the version but it supports more actions:
<?php
$loop = React\EventLoop\Factory::create();
$factory = new Factory($loop);
$client = $factory->createClient('http://10.0.0.2:8000/');
$client->version()->then(function ($version) {
var_dump($version);
});
$loop->run();
Examples
All the examples from this post can be found on Github.
Conclusion
Together with react/socket
, react/socket-client
provides the tools to communicate with remote servers and services. While they look like a small wrapper around streams they make for a lot more possibilities then just local streams. The tic tac toe example is just a simple one, imagen implementing niche protocols or sending out a bunch of HTTP connections in one go. react/socket-client
makes that possible.