Created
January 25, 2023 13:25
-
-
Save mudassaralichouhan/59e436d80cc465068a191bca964f5a65 to your computer and use it in GitHub Desktop.
web-socket
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\WebSocket; | |
class Notification extends WebSocket | |
{ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use App\Controllers\Auth\Authorizer; | |
use App\WebSocket\WebSocket; | |
require __DIR__ . '/bootstrap.php'; | |
$address = gethostbyname('localhost'); | |
$port = '9000'; | |
$socket = new WebSocket($address, $port, 0.1); | |
$approved = function ($token) { | |
$class = Authorizer::getModal(preg_replace('/\d/', '', $token)); | |
$id = str_replace('#', '', $token); | |
$_ENV['database.database'] = 'userdb_347_28385'; | |
if ((new $class())->select('1')->find($id)) | |
return true; | |
return false; | |
}; | |
$filter_token = function ($request) { | |
preg_match('([#]+[0-9]+)', $request, $request); | |
return $request[0]; | |
}; | |
$socket->start($approved, $filter_token); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\WebSocket; | |
use Exception; | |
use Socket; | |
class WebSocket | |
{ | |
private array $clients = []; | |
private Socket $server; | |
private int $sleep = 0; | |
public function __construct(string $address, int $port, int $sleep) | |
{ | |
//Create TCP/IP stream socket | |
if (!$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) { | |
$error_code = socket_last_error(); | |
$error_message = socket_strerror($error_code); | |
die("Couldn't create socket: [$error_code] $error_message \n"); | |
} | |
echo "Socket: created ws://$address:$port \n"; | |
socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1); | |
//bind socket to specified host | |
if (!socket_bind($server, $address, $port)) { | |
$error_code = socket_last_error(); | |
$error_message = socket_strerror($error_code); | |
die("Could not bind socket : [$error_code] $error_message \n"); | |
} | |
echo "Socket: bind $address:$port \n"; | |
if (!socket_listen($server)) { | |
$error_code = socket_last_error(); | |
$error_message = socket_strerror($error_code); | |
die("Could not listen on socket : [$error_code] $error_message \n"); | |
} | |
echo "Socket: listen $port \n"; | |
$this->server = $server; | |
//create & add listening socket to the list | |
$this->clients = [$this->server]; | |
$this->sleep = $sleep; | |
} | |
public function start($approved, $filter_token) | |
{ | |
try { | |
//start endless loop, so that our script doesn't stop | |
while (true) { | |
// manage multiple connections | |
$changed = $this->clients; | |
// returns the socket resources in $changed array | |
socket_select($changed, $write, $except, 1, 10); | |
// check for new socket | |
if (in_array($this->server, $changed)) { | |
$server_new = socket_accept($this->server); // accept new socket | |
$request = socket_read($server_new, 1024); //read data sent by the socket | |
$upgrade = $this->handshaking($request); //perform websocket handshake | |
$ip = $this->getIP($server_new); | |
$token = $filter_token($request); | |
if ($approved($token)) { | |
$this->clients[$token] = $server_new; // add socket to client array | |
@socket_write($server_new, $upgrade, strlen($upgrade)); | |
// notify all users about new connection | |
$this->send('system', [$ip . ' connected'], $this->clients); | |
echo 'Socket: Connect ' . $ip . ' with auth-token ' . $token . PHP_EOL; | |
} else { | |
echo 'Socket: denied ' . $ip . ' with auth-token ' . $token . PHP_EOL; | |
} | |
// make room for new socket | |
$found_socket = array_search($this->server, $changed); | |
unset($changed[$found_socket]); | |
} | |
// loop through all connected sockets | |
foreach ($changed as $token => $changed_socket) { | |
// check for any incoming data | |
while (socket_recv($changed_socket, $buf, 1024, 0) >= 1) { | |
//prepare data to be sent to client | |
$client_res = json_decode($this->unmask($buf)); | |
if ($client_res->receiver_to ?? false) { | |
$client_res->receiver_to = array_unique((array)$client_res->receiver_to); | |
if (($req_collection = array_intersect_key($this->clients, array_flip($client_res->receiver_to))) !== []) | |
$this->send('client', [$client_res], $req_collection); | |
else | |
echo 'Socket: not sending... data ' . PHP_EOL; | |
} else { | |
$this->send('system', ['set attr `[receiver_to]` ids'], [$this->clients[$token]]); | |
echo 'Socket: not set sender information attribute' . PHP_EOL; | |
} | |
break 2; | |
} | |
$buf = @socket_read($changed_socket, 1024, PHP_NORMAL_READ); | |
if ($buf === false) { | |
// remove client for $clients array | |
$found_socket = array_search($changed_socket, $this->clients); | |
$ip = $this->getIP($changed_socket); | |
unset($this->clients[$found_socket]); | |
// notify all users about disconnected connection | |
$this->send('system', [$ip . 'disconnected'], $this->clients); | |
echo 'Socket: sleep socket for ' . $ip . PHP_EOL; | |
} | |
echo 'Socket: socket is running ' . PHP_EOL; | |
echo 'Socket: selected ' . count($changed) . PHP_EOL; | |
} | |
echo 'Socket: listing ' . count($this->clients) . PHP_EOL; | |
sleep($this->sleep); | |
} | |
} catch (Exception $e) { | |
echo $e->getMessage(); | |
socket_close($this->server); | |
} | |
} | |
public function send(string $type, array $response, array $clients): void | |
{ | |
$msg = $this->mask($type, $response); | |
foreach ($clients as $token => $changed_socket) { | |
@socket_write($changed_socket, $msg, strlen($msg)); | |
echo 'Socket: sending... data to ' . $token . PHP_EOL; | |
} | |
} | |
// Unmask incoming framed message | |
public function unmask(string $buffer): string | |
{ | |
$length = ord($buffer[1]) & 127; | |
if ($length == 126) { | |
$masks = substr($buffer, 4, 4); | |
$data = substr($buffer, 8); | |
} elseif ($length == 127) { | |
$masks = substr($buffer, 10, 4); | |
$data = substr($buffer, 14); | |
} else { | |
$masks = substr($buffer, 2, 4); | |
$data = substr($buffer, 6); | |
} | |
$buffer = ''; | |
for ($i = 0; $i < strlen($data); ++$i) { | |
$buffer .= $data[$i] ^ $masks[$i % 4]; | |
} | |
return $buffer; | |
} | |
// Encode message for transfer to client. | |
public function mask(string $type, array $response): string | |
{ | |
$b1 = 0x80 | (0x1 & 0x0f); | |
$res = json_encode(['type' => $type, 'data' => $response]); | |
$length = strlen($res); | |
if ($length <= 125) | |
$header = pack('CC', $b1, $length); | |
elseif ($length > 125 && $length < 65536) | |
$header = pack('CCn', $b1, 126, $length); | |
elseif ($length >= 65536) | |
$header = pack('CCNN', $b1, 127, $length); | |
return $header . $res; | |
} | |
public function handshaking($received_header): string | |
{ | |
$lines = preg_split("/\r\n/", $received_header); | |
foreach ($lines as $line) { | |
$line = chop($line); | |
if (preg_match('/\A(\S+): (.*)\z/', $line, $matches)) { | |
$headers[$matches[1]] = $matches[2]; | |
} | |
} | |
$secKey = $headers['Sec-WebSocket-Key'] ?? ''; | |
$secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))); | |
//hand shaking header | |
return "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" . | |
"Upgrade: websocket\r\n" . | |
"Connection: Upgrade\r\n" . | |
"Sec-WebSocket-Accept:$secAccept\r\n\r\n"; | |
} | |
private function getIP(Socket $socket) | |
{ | |
socket_getpeername($socket, $ip); | |
return $ip; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment