Created
January 18, 2023 16:48
-
-
Save mudassaralichouhan/19be8c5f6738b38e167675c6c14666f9 to your computer and use it in GitHub Desktop.
Socket programming in PHP [mini chat app]
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
<!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> |
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 | |
$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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
PHP_VERSION >= 7.4
~#
php server.php
index.html
file in browser