Skip to content

Commit

Permalink
multiple client and adventure management socket/client
Browse files Browse the repository at this point in the history
  • Loading branch information
muhammetsafak committed Dec 1, 2024
1 parent 691befd commit 48024be
Show file tree
Hide file tree
Showing 9 changed files with 465 additions and 102 deletions.
41 changes: 28 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ public function live(callable $callback): void;
public function wait(int $second): void;
```

**`broadcast()` :**

```php
public function broadcast(string $message, array|string|int|null $clients = null): bool;
```

#### Special methods for TLS and SSL.

TLS and SSL work similarly.
Expand Down Expand Up @@ -143,23 +149,32 @@ _**Example :**_
```php
require_once "../vendor/autoload.php";
use \InitPHP\Socket\Socket;
use \InitPHP\Socket\Interfaces\SocketServerInterface;
use \InitPHP\Socket\Interfaces\{SocketServerInterface, SocketServerClientInterface};

$server = Socket::server(Socket::TLS, '127.0.0.1', 8080);
$server->connection();

$server->live(function (SocketServerInterface $socket) {
switch ($socket->read()) {
case 'exit' :
$socket->write('Goodbye!');
return;
case 'write' :
$socket->write('Run write command.');
break;
case 'read' :
$socket->write('Run read command.');
break;
default: return;
$server->live(function (SocketServerInterface $socket, SocketServerClientInterface $client) {
$read = $client->read();
if (!$read) {
return;
}
if (in_array($read, ['exit', 'quit'])) {
$client->push("Goodbye!");
$client->close();
return;
} else if (preg_match('/^REGISTER\s+([\w]{3,})$/i', $read, $matches)) {
// REGISTER admin
$name = trim(mb_substr($read, 9));
$socket->clientRegister($name, $client);
} else if (preg_match('/^SEND\s@([\w]+)\s(.*)$/i', $read, $matches)) {
// SEND @admin Hello World
$pushSocketName = $matches[1];
$message = $matches[2];
$socket->broadcast($message, [$pushSocketName])
} else {
$message = trim($read);
!empty($message) && $socket->broadcast($message);
}
});
```
Expand Down
5 changes: 5 additions & 0 deletions src/Common/BaseCommon.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@

use InitPHP\Socket\Exception\{SocketException, SocketInvalidArgumentException};

use const AF_INET;
use const AF_INET6;
use const AF_UNIX;

use function getprotobyname;
use function socket_create;
use function socket_last_error;
Expand Down Expand Up @@ -73,6 +77,7 @@ public function getSocket()
if(!isset($this->socket)){
throw new SocketException('The socket cannot be reachable before the connection is made.');
}

return $this->socket;
}

Expand Down
91 changes: 85 additions & 6 deletions src/Common/BaseServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,111 @@

namespace InitPHP\Socket\Common;

use InitPHP\Socket\Interfaces\SocketServerClientInterface;
use InitPHP\Socket\Interfaces\SocketServerInterface;

use InitPHP\Socket\Server\ServerClient;
use function sleep;
use function usleep;
use function call_user_func_array;
use function socket_accept;
use function array_search;
use function is_iterable;
use function is_int;

abstract class BaseServer implements SocketServerInterface
{
use BaseCommon;

/** @var SocketServerClientInterface[] */
protected array $clients = [];

/** @var array<string|int, int> */
protected array $clientMap = [];

abstract public function connection(): SocketServerInterface;

abstract public function disconnect(): bool;

abstract public function read(int $length = 1024): ?string;
public function getClients(): array
{
return $this->clients;
}

public function clientRegister($id, SocketServerClientInterface $client): bool
{
try {
$index = array_search($client, $this->clients);
if ($index === false) {
return false;
}
$this->clientMap[$id] = $index;
$this->clients[$index]->setId($id);

return true;
} catch (\Throwable $e) {
return false;
}
}

/**
* @param string $message
* @param array|string|int|null $clients
* @return bool
*/
public function broadcast(string $message, $clients = null): bool
{
try {
if ($clients !== null) {
!is_iterable($clients) && $clients = [$clients];
foreach ($clients as $id) {
isset($this->clients[$this->clientMap[$id]]) && $this->clients[$this->clientMap[$id]]->push($message);
}
} else {
foreach ($this->clients as $address => $client) {
$client->push($message);
}
}

abstract public function write(string $string): ?int;
return true;
} catch (\Throwable $e) {
return false;
}
}

public function live(callable $callback): void
/**
* @inheritDoc
*/
public function live(callable $callback, int $usleep = 100000): void
{
while (true) {
$callback($this);
if ($clientSocket = socket_accept($this->socket)) {
$client = (new ServerClient())->__setSocket($clientSocket);
$this->clients[] = $client;
}
foreach ($this->clients as $index => $client) {
if ($client->isDisconnected()) {
unset($this->clients[$index]);
continue;
}
call_user_func_array($callback, [$this, $client]);
}

$usleep < 1000 && $usleep = 1000;
$this->wait($usleep / 1000000);
}
}

public function wait(int $second): void
public function wait($second): void
{
sleep($second);
if ($second < 0) {
throw new \InvalidArgumentException("Waiting time cannot be less than 0.");
}
if (is_int($second)) {
sleep($second);
} else {
usleep($second * 1000000);
}
}

}
75 changes: 47 additions & 28 deletions src/Common/StreamServerTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

namespace InitPHP\Socket\Common;

use InitPHP\Socket\Server\ServerClient;
use InitPHP\Socket\Socket;
use InitPHP\Socket\Exception\{SocketConnectionException, SocketException, SocketInvalidArgumentException};

use const STREAM_CRYPTO_METHOD_SSLv2_SERVER;
Expand All @@ -32,9 +34,6 @@
use function ini_get;
use function stream_socket_accept;
use function fclose;
use function fread;
use function fwrite;
use function strlen;
use function stream_set_timeout;
use function stream_set_blocking;
use function stream_socket_enable_crypto;
Expand All @@ -48,10 +47,6 @@ trait StreamServerTrait

protected array $options = [];


/** @var resource */
protected $accept;

public function __construct(string $host, int $port, $argument)
{
$this->setHost($host)->setPort($port);
Expand All @@ -75,49 +70,66 @@ public function connection(): self
throw new SocketConnectionException('Connection Error : ' . $errStr);
}
$this->socket = $socket;
$this->accept = $accept;

$this->clients[] = (new ServerClient([
'type' => $this->type === 'tls' ? Socket::TLS : Socket::SSL,
'host' => $this->getHost(),
'port' => $this->getPort(),
]))->__setSocket($accept);

return $this;
}

public function disconnect(): bool
{
if(isset($this->socket)){
fclose($this->socket);
}
if(isset($this->accept)){
fclose($this->accept);
if (!empty($this->clients)) {
foreach ($this->clients as $client) {
$client->close();
}
}
return true;
}

public function read(int $length = 1024): ?string
{
$read = fread($this->getSocket(), $length);
return $read === FALSE ? null : $read;
}
if(!empty($this->socket)){
fclose($this->socket);
}

public function write(string $string): ?int
{
$write = fwrite($this->getSocket(), $string, strlen($string));
return $write === FALSE ? null : $write;
return true;
}

public function timeout(int $second): self
{
stream_set_timeout($this->accept, $second);
if (!empty($this->clients)) {
foreach ($this->clients as $client) {
stream_set_timeout($client->getSocket(), $second);
}
ServerClient::__setCallbacks('stream_set_timeout', ['{socket}', $second]);
}

return $this;
}

public function blocking(bool $mode = true): self
{
stream_set_blocking($this->accept, $mode);
if (!empty($this->clients)) {
foreach ($this->clients as $client) {
stream_set_blocking($client->getSocket(), $mode);
}
ServerClient::__setCallbacks('stream_set_blocking', ['{socket}', $mode]);
}


return $this;
}

public function crypto(?string $method = null): self
{
if(empty($method)){
stream_socket_enable_crypto($this->accept, false);
if (!empty($this->clients)) {
foreach ($this->clients as $client) {
stream_socket_enable_crypto($client->getSocket(), false);
}
ServerClient::__setCallbacks('stream_socket_enable_crypto', ['{socket}', false]);
}

return $this;
}
$method = strtolower($method);
Expand All @@ -134,7 +146,14 @@ public function crypto(?string $method = null): self
if(!isset($algos[$method])){
throw new SocketException('Unsupported crypto method. This library supports: ' . implode(', ', array_keys($algos)));
}
stream_socket_enable_crypto($this->accept, true, $algos[$method]);

if (!empty($this->clients)) {
foreach ($this->clients as $client) {
stream_socket_enable_crypto($client->getSocket(), true, $algos[$method]);
}
ServerClient::__setCallbacks('stream_socket_enable_crypto', ['{socket}', true, $algos[$method]]);
}

return $this;
}

Expand Down
47 changes: 47 additions & 0 deletions src/Interfaces/SocketServerClientInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace InitPHP\Socket\Interfaces;

interface SocketServerClientInterface
{

/**
* @param int|string $id
* @return self
*/
public function setId($id): self;

/**
* @return string|int|null
*/
public function getId();

/**
* @param string $message
* @return int|false
*/
public function push(string $message);

/**
* @param int $length
* @param int|null $type
* @return string|false
*/
public function read(int $length = 1024, ?int $type = null);

/**
* @return bool
*/
public function close(): bool;

/**
* @return false|resource|\Socket
*/
public function getSocket();

/**
* @return bool
*/
public function isDisconnected(): bool;

}
Loading

0 comments on commit 48024be

Please sign in to comment.