Skip to content

Instantly share code, notes, and snippets.

@mudassaralichouhan
Created January 18, 2023 16:48
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 mudassaralichouhan/19be8c5f6738b38e167675c6c14666f9 to your computer and use it in GitHub Desktop.
Save mudassaralichouhan/19be8c5f6738b38e167675c6c14666f9 to your computer and use it in GitHub Desktop.
Socket programming in PHP [mini chat app]
<!DOCTYPE html>
<html lang="">
<head>
<meta charset='UTF-8'/>
<title>Chat Room</title>
</head>
<body>
<input type="text" name="name">
<input type="text" name="message"/>
<input type="text" name="receiver-ids"/>
<button>Send</button>
<script>
(() => {
const address = '127.0.0.1';
const port = '9000';
const token = prompt('token');
if (token === '')
location.reload();
const websocket = new WebSocket(`ws://${address}:${port}?token=${token}`);
websocket.onopen = (res) => {
console.log('socket open', res);
document.body.insertAdjacentHTML('afterbegin', `<p>socket open: ${res.explicitOriginalTarget.url}</p>`);
}
websocket.onerror = (res) => {
console.log('socket error', res);
document.body.insertAdjacentHTML('afterbegin', `<p>socket error: ${res.explicitOriginalTarget.url}</p>`);
};
websocket.onclose = (res) => {
console.log('socket close', res);
document.body.insertAdjacentHTML('afterbegin', `<p>socket close: ${res.explicitOriginalTarget.url}</p>`);
};
websocket.onmessage = (res) => {
if (res.isTrusted) {
const response = JSON.parse(res.data);
console.log(response);
document.body.insertAdjacentHTML('afterend', `<p>${res.data}</p>`);
return;
}
console.log('socket not trusted', res);
document.body.insertAdjacentHTML('afterbegin', `<p>socket not-trusted: ${res.explicitOriginalTarget.url}</p>`);
};
document.querySelector('button').onclick = (e) => {
const name = document.querySelector('input[name="name"]').value;
const message = document.querySelector('input[name="message"]').value;
const receiverIds = document.querySelector('input[name="receiver-ids"]').value;
websocket.send(JSON.stringify({
name: name,
message: message,
reciver_to: receiverIds.replace(token).split(/[\s,\-|=]+/)
}));
};
})();
</script>
</body>
</html>
<?php
$address = gethostbyname('localhost');
$port = '9000';
//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";
//create & add listening socket to the list
$clients = [$server];
//start endless loop, so that our script doesn't stop
try {
while (true) {
// manage multiple connections
$changed = $clients;
// returns the socket resources in $changed array
socket_select($changed, $write, $except, 1, 10);
// check for new socket
if (in_array($server, $changed)) {
$server_new = socket_accept($server); // accept new socket
$request = socket_read($server_new, 1024); //read data sent by the socket
$token = get_token($request);
if (in_array($token, [1,2,3,4,5,6,7,8,9])) {
$clients[$token] = $server_new; // add socket to client array
perform_handshaking($request, $server_new, true); //perform websocket handshake
socket_getpeername($server_new, $ip); // get ip address of connected socket
send_message(mask('system', [$ip . ' connected']), $clients); // notify all users about new connection
echo 'Socket: Connect ' . $ip . ' with auth-token ' . $token . PHP_EOL;
// make room for new socket
$found_socket = array_search($server, $changed);
unset($changed[$found_socket]);
} else {
perform_handshaking($request, $server_new);
socket_getpeername($server_new, $ip);
echo 'Socket: denied ' . $ip . ' with auth-token ' . $token . PHP_EOL;
}
}
// loop through all connected sockets
foreach ($changed as $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(unmask($buf));
if ($client_res->reciver_to ?? false) {
$client_res->reciver_to = array_unique((array)$client_res->reciver_to);
if (($req_collection = array_intersect_key($clients, array_flip($client_res->reciver_to))) !== [])
send_message(mask('client', [$client_res]), $req_collection);
else
echo 'Socket: not sending... data ' . PHP_EOL;
} else {
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, $clients);
socket_getpeername($changed_socket, $ip);
unset($clients[$found_socket]);
// notify all users about disconnected connection
send_message(mask('system', [$ip . 'disconnected']), $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($clients) . PHP_EOL;
sleep(0.1);
}
} catch (Exception $e) {
socket_close($server);
echo $e->getMessage();
}
function send_message(string $msg, array $clients): bool
{
foreach ($clients as $token => $changed_socket) {
@socket_write($changed_socket, $msg, strlen($msg));
echo 'Socket: sending... data to '. $token . PHP_EOL;
}
return true;
}
// Unmask incoming framed message
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.
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;
}
function perform_handshaking($received_header, $client_conn, $connect = false)
{
$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
$upgrade = "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";
if ($connect)
@socket_write($client_conn, $upgrade, strlen($upgrade));
}
function get_token($request): int
{
if (preg_match('/token=[^\s]+/', $request, $matches))
return (int) str_replace('token=', '', $matches[0]);
return 0;
}
@mudassaralichouhan
Copy link
Author

PHP_VERSION >= 7.4

  • ~# php server.php
  • open index.html file in browser
  • then: select you unique ID

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment