Added SocketServer and SocketClient
This commit is contained in:
parent
4b960e92d5
commit
834ece2645
145
app/Classes/Sock/SocketClient.php
Normal file
145
app/Classes/Sock/SocketClient.php
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Sock;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
final class SocketClient {
|
||||
public \Socket $connection; // @todo make private
|
||||
private string $address = '';
|
||||
private int $port = 0;
|
||||
|
||||
public function __construct (\Socket $connection) {
|
||||
socket_getsockname($connection,$this->address,$this->port);
|
||||
Log::info(sprintf('Connection from [%s] on port [%d]',$this->address,$this->port),['m'=>__METHOD__]);
|
||||
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $timeout
|
||||
* @return int
|
||||
*/
|
||||
public function canSend(int $timeout): int
|
||||
{
|
||||
$write = [$this->connection];
|
||||
|
||||
return $this->socketSelect(NULL,$write,NULL,$timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection with the client
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
socket_shutdown($this->connection);
|
||||
socket_close($this->connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a client socket
|
||||
* @return static
|
||||
*/
|
||||
public static function create(string $address,int $port): self
|
||||
{
|
||||
Log::debug(sprintf('Creating connection to [%s:%d]',$address,$port));
|
||||
|
||||
$address = gethostbyname($address);
|
||||
|
||||
/* Create a TCP/IP socket. */
|
||||
$socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
|
||||
if ($socket === FALSE)
|
||||
throw new SocketException(SocketException::CANT_CREATE_SOCKET,socket_strerror(socket_last_error($socket)));
|
||||
|
||||
$result = socket_connect($socket,$address,$port);
|
||||
if ($result === FALSE)
|
||||
throw new SocketException(SocketException::CANT_CONNECT,socket_strerror(socket_last_error($socket)));
|
||||
|
||||
return new self($socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the client's address
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAddress(): string
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the port in use
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getPort(): int
|
||||
{
|
||||
return $this->port;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $timeout
|
||||
* @return int
|
||||
* @note use socketSelect()
|
||||
* @todo Node used by bink yet?
|
||||
* @todo to test
|
||||
*/
|
||||
public function hasData(int $timeout): int
|
||||
{
|
||||
$read = [$this->connection];
|
||||
$write = $except = NULL;
|
||||
|
||||
//$rc = socket_select($read,$write,$except,$timeout);
|
||||
//return $rc;
|
||||
return $this->socketSelect($read,NULL,NULL,$timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send data to the client
|
||||
*
|
||||
* @param $message
|
||||
* @param int $timeout
|
||||
* @param null $length
|
||||
* @return false|int
|
||||
*/
|
||||
public function send($message,int $timeout,$length=NULL) {
|
||||
if ($timeout AND (! $rc = $this->canSend($timeout)))
|
||||
return $rc;
|
||||
|
||||
if (is_null($length))
|
||||
$length = strlen($message);
|
||||
|
||||
return socket_write($this->connection,$message,$length);
|
||||
}
|
||||
|
||||
private function socketSelect(?array $read,?array $write,?array $except,int $timeout): int
|
||||
{
|
||||
return socket_select($read,$write,$except,$timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data from the socket.
|
||||
* If we only want 1 character, we'll return the ASCII value of the data received
|
||||
*
|
||||
* @param int $timeout
|
||||
* @param int $len
|
||||
* @return false|int|string|null
|
||||
*/
|
||||
public function read(int $timeout,int $len=1024)
|
||||
{
|
||||
Log::debug(sprintf('+ Start [%d]',$len),['m'=>__METHOD__]);
|
||||
|
||||
if ($timeout AND (! $rc = $this->hasData($timeout)))
|
||||
return $rc;
|
||||
|
||||
if (($buf=socket_read($this->connection,$len,PHP_BINARY_READ)) === FALSE) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Log::debug(sprintf(' - Read [%d]',strlen($buf)),['m'=>__METHOD__]);
|
||||
|
||||
// For single character reads, we'll return the ASCII value of the buf
|
||||
return ($len == 1 and (ord($buf) != 0)) ? ord($buf) : $buf;
|
||||
}
|
||||
}
|
27
app/Classes/Sock/SocketException.php
Normal file
27
app/Classes/Sock/SocketException.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Sock;
|
||||
|
||||
final class SocketException extends \Exception {
|
||||
public const CANT_CREATE_SOCKET = 1;
|
||||
public const CANT_BIND_SOCKET = 2;
|
||||
public const CANT_LISTEN = 3;
|
||||
public const CANT_ACCEPT = 4;
|
||||
public const CANT_CONNECT = 5;
|
||||
|
||||
private array $messages = [
|
||||
self::CANT_CREATE_SOCKET => 'Can\'t create socket: "%s"',
|
||||
self::CANT_BIND_SOCKET => 'Can\'t bind socket: "%s"',
|
||||
self::CANT_LISTEN => 'Can\'t listen: "%s"',
|
||||
self::CANT_ACCEPT => 'Can\'t accept connections: "%s"',
|
||||
self::CANT_CONNECT => 'Can\'t connect: "%s"',
|
||||
];
|
||||
|
||||
public function __construct(int $code,string $params=NULL) {
|
||||
$message = $params
|
||||
? call_user_func_array('sprintf',[$this->messages[$code],$params])
|
||||
: $this->messages[$code];
|
||||
|
||||
parent::__construct($message,$code);
|
||||
}
|
||||
}
|
114
app/Classes/Sock/SocketServer.php
Normal file
114
app/Classes/Sock/SocketServer.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classes\Sock;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
final class SocketServer {
|
||||
private $server; // Our server resource
|
||||
private string $bind; // The IP address to bind to
|
||||
private int $port; // The Port to bind to
|
||||
private int $backlog = 5; // Number of incoming connections queued
|
||||
|
||||
private array $handler; // The class and method that will handle our connection
|
||||
|
||||
public function __construct(int $port,string $bind='0.0.0.0')
|
||||
{
|
||||
$this->bind = $bind;
|
||||
$this->port = $port;
|
||||
|
||||
$this->_init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind to our Socket
|
||||
*
|
||||
* @throws SocketException
|
||||
*/
|
||||
private function _bindSocket(): void
|
||||
{
|
||||
if (socket_bind($this->server,$this->bind,$this->port) === FALSE)
|
||||
throw new SocketException(SocketException::CANT_BIND_SOCKET,socket_strerror(socket_last_error($this->server)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create our Socket
|
||||
*
|
||||
* @throws SocketException
|
||||
*/
|
||||
private function _createSocket(): void
|
||||
{
|
||||
/**
|
||||
* Check dependencies
|
||||
*/
|
||||
if (! extension_loaded('sockets'))
|
||||
throw new SocketException(SocketException::CANT_ACCEPT,'Missing sockets extension');
|
||||
|
||||
if (! extension_loaded('pcntl'))
|
||||
throw new SocketException(SocketException::CANT_ACCEPT,'Missing pcntl extension');
|
||||
|
||||
$this->server = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
|
||||
|
||||
if ($this->server === FALSE)
|
||||
throw new SocketException(SocketException::CANT_CREATE_SOCKET,socket_strerror(socket_last_error()));
|
||||
|
||||
socket_set_option($this->server,SOL_SOCKET,SO_REUSEADDR,1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup Socket and Bind
|
||||
*
|
||||
* @throws SocketException
|
||||
*/
|
||||
private function _init(): void
|
||||
{
|
||||
$this->_createSocket();
|
||||
$this->_bindSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Our main loop where we listen for connections
|
||||
*
|
||||
* @throws SocketException
|
||||
*/
|
||||
public function listen()
|
||||
{
|
||||
if (! $this->handler)
|
||||
throw new SocketException(SocketException::CANT_LISTEN,'Handler not set.');
|
||||
|
||||
if (socket_listen($this->server,$this->backlog) === FALSE)
|
||||
throw new SocketException(SocketException::CANT_LISTEN,socket_strerror(socket_last_error($this->server)));
|
||||
|
||||
Log::info(sprintf('Listening on [%s:%d]',$this->bind,$this->port),['m'=>__METHOD__]);
|
||||
|
||||
$this->loop();
|
||||
socket_close($this->server);
|
||||
|
||||
Log::info(sprintf('CLosed [%s:%d]',$this->bind,$this->port),['m'=>__METHOD__]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage and execute incoming connecitons
|
||||
*
|
||||
* @throws SocketException
|
||||
*/
|
||||
private function loop()
|
||||
{
|
||||
while (TRUE) {
|
||||
if (($accept = socket_accept($this->server)) === FALSE)
|
||||
throw new SocketException(SocketException::CANT_ACCEPT,socket_strerror(socket_last_error($this->server)));
|
||||
|
||||
$this->handler[0]->{$this->handler[1]}(new SocketClient($accept));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set our connection handler Class and Method
|
||||
*
|
||||
* @param array $handler
|
||||
*/
|
||||
public function setConnectionHandler(array $handler): void
|
||||
{
|
||||
$this->handler = $handler;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user