Skip to content

Instantly share code, notes, and snippets.

@williamdes
Last active May 8, 2021 09:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save williamdes/ef6c3601bec753d9b7c94d3eb2f48d6a to your computer and use it in GitHub Desktop.
Save williamdes/ef6c3601bec753d9b7c94d3eb2f48d6a to your computer and use it in GitHub Desktop.
PHP Socket client server example
<?php
declare(strict_types = 1);
require_once __DIR__ . DIRECTORY_SEPARATOR . 'WorkerSocket.php';
$w = new WorkerSocket(
__DIR__ . DIRECTORY_SEPARATOR . 'server.sock'
);
$w->connect(__DIR__ . '/' . time() . '-client.sock');
$i = 10;
while ($i > 0) {
[$buf, $from] = $w->sendMessage('Hi number: ' . --$i);
echo '[DEBUG]' . ' Received ' . $buf . ' from ' . $from . PHP_EOL;
sleep(1);
}
$w->sendMessage('STOP');
?>

PHP Socket client server example

With this example you will be able to use modern PHP code and have multiple clients connected.

The code is inspired and sourced from the of the posted notes here.

If you CTRL-C stopped the server, run the script twice. The first time will clean up the left over socket. Or just remove the socket file before starting the server.

There is a "magic" command (STOP) in the code to stop the server, you can easily remove it.

server console

$ php -f server.php 
[DEBUG] Ready to receive...
[DEBUG] Received Hi number: 9 from /mnt/Dev/@williamdes/php/1620432655-client.sock
[DEBUG] Request processed
[DEBUG] Ready to receive...
[DEBUG] Received Hi number: 8 from /mnt/Dev/@williamdes/php/1620432655-client.sock
[DEBUG] Request processed
[DEBUG] Ready to receive...
[DEBUG] Received Hi number: 7 from /mnt/Dev/@williamdes/php/1620432655-client.sock
[DEBUG] Request processed
[DEBUG] Ready to receive...
[DEBUG] Received Hi number: 6 from /mnt/Dev/@williamdes/php/1620432655-client.sock
[DEBUG] Request processed
[DEBUG] Ready to receive...
[DEBUG] Received Hi number: 5 from /mnt/Dev/@williamdes/php/1620432655-client.sock
[DEBUG] Request processed
[DEBUG] Ready to receive...
[DEBUG] Received Hi number: 4 from /mnt/Dev/@williamdes/php/1620432655-client.sock
[DEBUG] Request processed
[DEBUG] Ready to receive...
[DEBUG] Received Hi number: 3 from /mnt/Dev/@williamdes/php/1620432655-client.sock
[DEBUG] Request processed
[DEBUG] Ready to receive...
[DEBUG] Received Hi number: 2 from /mnt/Dev/@williamdes/php/1620432655-client.sock
[DEBUG] Request processed
[DEBUG] Ready to receive...
[DEBUG] Received Hi number: 1 from /mnt/Dev/@williamdes/php/1620432655-client.sock
[DEBUG] Request processed
[DEBUG] Ready to receive...
[DEBUG] Received Hi number: 0 from /mnt/Dev/@williamdes/php/1620432655-client.sock
[DEBUG] Request processed
[DEBUG] Ready to receive...
[DEBUG] Received STOP from /mnt/Dev/@williamdes/php/1620432655-client.sock
[DEBUG] Request processed
[DEBUG] Disconnecting

worker 1 console

$ php -f client.php 
[DEBUG] Received Hi number: 9->Response from /mnt/Dev/@williamdes/php/server.sock
[DEBUG] Received Hi number: 8->Response from /mnt/Dev/@williamdes/php/server.sock
[DEBUG] Received Hi number: 7->Response from /mnt/Dev/@williamdes/php/server.sock
[DEBUG] Received Hi number: 6->Response from /mnt/Dev/@williamdes/php/server.sock
[DEBUG] Received Hi number: 5->Response from /mnt/Dev/@williamdes/php/server.sock
[DEBUG] Received Hi number: 4->Response from /mnt/Dev/@williamdes/php/server.sock
[DEBUG] Received Hi number: 3->Response from /mnt/Dev/@williamdes/php/server.sock
[DEBUG] Received Hi number: 2->Response from /mnt/Dev/@williamdes/php/server.sock
[DEBUG] Received Hi number: 1->Response from /mnt/Dev/@williamdes/php/server.sock
[DEBUG] Received Hi number: 0->Response from /mnt/Dev/@williamdes/php/server.sock
[DEBUG] Disconnecting
<?php
declare(strict_types = 1);
require_once __DIR__ . DIRECTORY_SEPARATOR . 'WorkerSocket.php';
$w = new WorkerSocket(
__DIR__ . DIRECTORY_SEPARATOR . 'server.sock'
);
$w->connect();
$w->listen();
?>
<?php
declare(strict_types = 1);
/**
* @license UNLICENSE
* @co-author William Desportes <williamdes [at] wdes [dot] fr>
* @source https://www.php.net/manual/fr/function.socket-create.php#108535
*/
class WorkerSocket
{
/**
* @var string
*/
private $serverSocketFile;
/**
* @var resource
*/
private $openSocket;
/**
* @var string
*/
private $openSocketFile;
/**
* @var bool
*/
private $listen = true;
public function __construct(string $serverSocketFile)
{
if (! extension_loaded('sockets')) {
echo '[ERROR]' . ' The sockets extension is not loaded.' . PHP_EOL;
exit(1);
}
$this->serverSocketFile = $serverSocketFile;
}
public function __destruct()
{
$this->disconnect();
}
public function connect(?string $clientSocket = null): void
{
$this->openSocketFile = $clientSocket === null ? $this->serverSocketFile : $clientSocket;
// create unix tcp socket
$socket = socket_create(AF_UNIX, SOCK_RAW, 0);
if ($socket === false) {
echo '[ERROR]' . ' Unable to create AF_UNIX socket.' . PHP_EOL;
exit(1);
}
$this->openSocket = $socket;
// same socket will be used in recv_from and send_to
if (! socket_bind($this->openSocket, $this->openSocketFile)) {
echo '[ERROR]' . ' Unable to bind to ' . $this->openSocketFile . PHP_EOL;
exit(1);
}
}
public function disconnect(): void
{
$this->listen = false;
// close socket and delete own .sock file
if (is_resource($this->openSocket)) {
socket_close($this->openSocket);
}
if (file_exists($this->openSocketFile)) {
unlink($this->openSocketFile);
}
$this->logger->debug('Disconnecting');
}
public function listen(): void
{
while ($this->listen) { // server never exits unless a magic command is sent
// receive query
if (!socket_set_block($this->openSocket)) {
echo '[ERROR]' . ' Unable to set blocking mode for socket' . PHP_EOL;
return;
}
echo '[DEBUG]' . ' Ready to receive...' . PHP_EOL;
// will block to wait client query
[$buf, $from] = $this->receive();
// NOTE: remove this code to disable the magic STOP command
if ($buf === 'STOP') {
$this->listen = false;
}
echo '[DEBUG]' . ' Received ' . $buf . ' from ' . $from . PHP_EOL;
$buf .= '->Response'; // process client query here
// send response
if (!socket_set_nonblock($this->openSocket)) {
echo '[ERROR]' . ' Unable to set nonblocking mode for socket' . PHP_EOL;
return;
}
// client side socket filename is known from client request: $from
if (! $this->send($buf, $from)) {
echo '[ERROR]' . ' Sending failed' . PHP_EOL;
continue;
}
echo '[DEBUG]' . ' Request processed' . PHP_EOL;
}
}
public function sendMessage(string $message): ?array
{
if (! $this->send($message, $this->serverSocketFile)) {
return null;
}
// use socket to receive data
if (!socket_set_block($this->openSocket)) {
echo '[ERROR]' . ' Unable to set blocking mode for socket' . PHP_EOL;
return null;
}
// will block to wait server response
return $this->receive();
}
private function send(string $msg, string $receivingAddress): bool
{
$len = strlen($msg);
// at this point 'server' process must be running and bound to receive from server.sock
$bytes_sent = socket_sendto($this->openSocket, $msg, $len, 0, $receivingAddress);
if ($bytes_sent === -1) {
echo '[ERROR]' . ' An error occurred while sending to the socket' . PHP_EOL;
return false;
}
if ($bytes_sent !== $len) {
echo '[ERROR]' . $bytes_sent . ' bytes have been sent instead of the ' . $len . ' bytes expected' . PHP_EOL;
return false;
}
return true;
}
/**
* @return string[]
*/
private function receive(): array
{
$buf = '';
$from = '';
// will block to wait server response
$bytes_received = socket_recvfrom($this->openSocket, $buf, MSG_WAITFORONE, 0, $from);
if ($bytes_received === -1) {
echo '[ERROR]' . ' An error occurred while receiving from the socket' . PHP_EOL;
return [$buf, $from];
}
return [$buf, $from];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment