Network¶
Unix socket¶
If you look to communicate between processes you should head to the IPC chapter.
Server¶
The first part to build a socket server is to accept incoming connections at an address:
use Innmind\IO\Sockets\Server;
use Innmind\Socket\Address\Unix;
use Innmind\Immutable\Sequence;
$server = $os
->sockets()
->open(Unix::of('/tmp/some-socket-name'))
->match(
static fn(Server $server) => $server,
static fn() => throw new \RuntimeException('Failed to open socket'),
);
$clients = Sequence::of();
while (true) {
$clients = $server
->watch()
->accept()
->toSequence()
->append($clients);
}
This will wait forever for a connection to open, when it does it's added to $clients
and then resume watching for new connections.
The next step is to define a protocol. Let's take a silly example where when connecting a client must send hello world\n
followed by their message ending with \n
. You can define such protocol like this:
use Innmind\IO\Readable\Frame\{
Chunk,
Line,
};
use Innmind\Immutable\Str;
$protocol = Chunk::of(12)
->flatMap(static fn(Str $hello) => Line::new())
->map(Str $message) => $message->rightTrim("\n"));
Chunk::of(12)
expresses the expected hello world string. flatMap
expresses what to do next with the read value, in this case we tell that we want a line ending with \n
. map
transform the line read previously to remove the \n
at the end since it's not part of the message.
Info
You can explore the Innmind\IO\Readable\Frame\
namespace to see the other kind of frames you can use. And you can also define your own.
You can look at innmind/http-parser
or innmind/amqp
for concrete examples of protocols defined this way.
Tip
Here the map
only modifies the message but you can change the value to any type you wish. It's even encouraged to encapsulate the data in your own classes to make sure it's the format you expect.
You can use then the procol like this:
use Innmind\IO\Sockets\Client;
use Innmind\Immutable\Str;
$server
->watch()
->accept()
->flatMap(
static fn(Client $client) => $client
->toEncoding(Str\Encoding::ascii)
->watch()
->frames($protocol)
->one(),
)
->match(
static fn(Str $message) => $message,
static fn() => null, // either no connection or failed to read the message
);
Sending data to the incoming connection is the same way as from the client side (see below).
Client¶
To connect to a server you can do:
use Innmind\IO\Sockets\Client;
use Innmind\Socket\Address\Unix;
$client = $os
->sockets()
->connectTo(Unix::of('/tmp/some-socket-name'))
->match(
static fn(Client $client) => $client,
static fn() => throw new \RuntimeException('Failed to connect'),
);
Then to send data:
use Innmind\Immutable\{
Str,
Sequence,
};
$client
->toEncoding(Str\Encoding::ascii)
->send(Sequence::of(
Str::of("hello world\nThis is your message\n"),
))
->match(
static fn() => null, // message sent
static fn() => throw new \RuntimeException('Failed to send'),
);
Info
As you can see send
expect a Sequence
of messages meaning you can send multiple ones. This is so you don't have to loop yourself.
In case you use a lazy sequence and you want to abort midway (say because a signal tells you to stop), you can do it like this:
$signaled = false;
$client
->abortWhen(static function() use (&$signaled) {
return $signaled;
})
->send($messages)
->match(
static fn() => null, // message sent
static fn() => throw new \RuntimeException('Failed to send'),
);
If the sending is aborted then it will always reach the error case, here meaning it will throw the exception.
If you want to read data coming from the server you'd do it the same way the server does (see above).
Over the wire¶
This works exactly the same way as unix sockets except for the method to open the server and the method to connect to it:
use Innmind\IO\Sockets\Server;
use Innmind\Socket\Internet\Transport;
use Innmind\IP\IP;
use Innmind\Url\Authority\Port;
$server = $os
->ports()
->open(
Transport::tcp(),
IP::v4('0.0.0.0'),
Port::of(8080),
)
->match(
static fn(Server $server) => $server,
static fn() => throw new \RuntimeException(),
);