153 lines
3.7 KiB
PHP
153 lines
3.7 KiB
PHP
<?php
|
|
|
|
namespace App\Classes\Sock;
|
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
final class SocketServer {
|
|
private const LOGKEY = 'SS-';
|
|
|
|
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 int $type; // Socket type
|
|
|
|
private array $handler; // The class and method that will handle our connection
|
|
|
|
public function __construct(int $port,string $bind='0.0.0.0',int $type=SOCK_STREAM)
|
|
{
|
|
$this->bind = $bind;
|
|
$this->port = $port;
|
|
$this->type = $type;
|
|
|
|
$this->createSocket();
|
|
|
|
if (socket_bind($this->server,$this->bind,$this->port) === FALSE)
|
|
throw new SocketException(SocketException::CANT_BIND_SOCKET,socket_strerror(socket_last_error($this->server)));
|
|
}
|
|
|
|
public function __get($key)
|
|
{
|
|
switch ($key) {
|
|
case 'handler':
|
|
return $this->handler;
|
|
default:
|
|
throw new \Exception('Unknown key: '.$key);
|
|
}
|
|
}
|
|
|
|
public function __set($key,$value)
|
|
{
|
|
switch ($key) {
|
|
case 'handler':
|
|
return $this->handler = $value;
|
|
default:
|
|
throw new \Exception('Unknown key: '.$key);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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');
|
|
|
|
switch ($this->type) {
|
|
case SOCK_STREAM:
|
|
$this->server = socket_create(AF_INET|AF_INET6,$this->type,SOL_TCP);
|
|
break;
|
|
|
|
case SOCK_DGRAM:
|
|
$this->server = socket_create(AF_INET|AF_INET6,$this->type,SOL_UDP);
|
|
break;
|
|
|
|
default:
|
|
throw new \Exception('Unknown socket_type:'.$this->type);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* 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 (in_array($this->type,[SOCK_STREAM,SOCK_SEQPACKET]))
|
|
if (socket_listen($this->server,$this->backlog) === FALSE)
|
|
throw new SocketException(SocketException::CANT_LISTEN,socket_strerror(socket_last_error($this->server)));
|
|
|
|
Log::info(sprintf('%s:- Listening on [%s:%d]',self::LOGKEY,$this->bind,$this->port));
|
|
|
|
switch ($this->type) {
|
|
case SOCK_STREAM:
|
|
$this->loop_tcp();
|
|
break;
|
|
|
|
case SOCK_DGRAM:
|
|
$this->loop_udp();
|
|
break;
|
|
}
|
|
|
|
socket_close($this->server);
|
|
|
|
Log::info(sprintf('%s:= Closed [%s:%d]',self::LOGKEY,$this->bind,$this->port));
|
|
}
|
|
|
|
/**
|
|
* Manage and execute incoming connections
|
|
*
|
|
* @throws SocketException
|
|
*/
|
|
private function loop_tcp(): void
|
|
{
|
|
while (TRUE) {
|
|
if (($accept = socket_accept($this->server)) === FALSE)
|
|
throw new SocketException(SocketException::CANT_ACCEPT,socket_strerror(socket_last_error($this->server)));
|
|
|
|
try {
|
|
$r = new SocketClient($accept);
|
|
|
|
} catch (\ErrorException $e) {
|
|
Log::error(sprintf('%s:! Creating Socket client failed? [%s]',self::LOGKEY,$e->getMessage()));
|
|
continue;
|
|
}
|
|
|
|
$this->handler[0]->{$this->handler[1]}($r);
|
|
}
|
|
}
|
|
|
|
private function loop_udp(): void
|
|
{
|
|
while (TRUE) {
|
|
$r = new SocketClient($this->server);
|
|
|
|
if ($r->hasData(30)) {
|
|
$this->handler[0]->{$this->handler[1]}($r);
|
|
|
|
// Sleep so our thread has a chance to pick up the data from our connection
|
|
usleep(50000);
|
|
}
|
|
}
|
|
}
|
|
} |