Skip to content

Instantly share code, notes, and snippets.

@root9b-zz
Last active June 16, 2016 13:27
Show Gist options
  • Save root9b-zz/e1f0b82769296b06c079e53c7362bb94 to your computer and use it in GitHub Desktop.
Save root9b-zz/e1f0b82769296b06c079e53c7362bb94 to your computer and use it in GitHub Desktop.
Proof of concept to show immediate, efficient message passing across clients without polling or DB or files using PHP sockets and server-sent events. By Matt Weeks.
<?php
// socketChatRoom proof of concept to show immediate, efficient message passing across clients
// without polling or DB or files using PHP sockets and server-sent events. By Matt Weeks.
//
// Instructions: put on a webserver and visit in a browser. Type chat messages and hit enter. Everybody on the page will see them.
//
// When invoked without parameters, this script will display a chat window for the browser.
// When invoked with a stream parameter, this script will start a chat broker if none exists
// and if one does exist, it will connect to it, then send new messages down in an event stream.
// If invoked with a POST containing a message, it will send the message to the broker to broadcast.
// Internet Explorer doesn't support server sent events but all other browsers have for a long time.
error_reporting(E_ALL);
$address = '127.12.34.56'; //Random localhost address
$port = 17760; //Random port
$BUFFER_SIZE = 2048;
//***********************************
//*********** Utility ***************
//***********************************
//Closes the given socket and removes it from the list
function killSock(&$sock, &$allSocks){
//Dead sock. Remove it.
foreach($allSocks as $offset => $allSock){
//Find the socket, then remove it from the allSocks set
if($allSock === $sock){
array_splice($allSocks, $offset, 1);
socket_close($sock);
break;
}
}
}
//Makes a socket or dies
function getSock(){
if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) {
error_log("socket_create() failed: " . socket_strerror(socket_last_error()) . " Your PHP install will not support socketChatRoom");
exit;
}
return $sock;
}
//****************************************
//************ Chat Window ***************
//****************************************
if( ! isset($_GET['stream']) && ! isset($_POST['message'])){
?>
<!DOCTYPE html>
<head>
<script>
function sendMsg(){
var m = document.getElementById("message");
var xhr = new XMLHttpRequest();
xhr.open("POST", document.location.href);
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.send("message=" + encodeURIComponent(m.value));
}
window.onload = function () {
window.evtSource = new EventSource(document.location.href + "?stream=1");
window.evtSource.addEventListener('text', function(e){
var t = document.getElementById("tarea");
t.appendChild(document.createTextNode("\n" + e.data));
});
}
</script>
</head>
<body>
<pre id="tarea">
Chat log:
</pre> <br>
<form onsubmit="sendMsg(); return false">
<input type="text" size="50" id="message">
</form>
</body>
<?php
exit;
//***********************************
//******** Send New Message *********
//***********************************
}elseif( isset($_POST['message'])){
$cliSock = getSock();
if (socket_connect($cliSock, $address, $port) !== false) {
$writable = json_encode(array('client' => $_SERVER['REMOTE_ADDR'] . ':' . $_SERVER['REMOTE_PORT'], 'message' => $_POST['message']));
if(socket_write($cliSock, $writable) === FALSE){
error_log("socket_write() failed: " . socket_strerror(socket_last_error($socket)));
}
}
socket_close($cliSock);
}elseif( isset($_GET['stream'])){
header("Content-Type: text/event-stream\n\n");
flush();
//***********************************
//************ Broker ***************
//***********************************
set_time_limit(0);// Allow the script to hang around waiting for connections.
$serverSock = getSock();
socket_set_option($serverSock, SOL_SOCKET, SO_LINGER, array('l_linger'=>0, 'l_onoff'=>0)); //Allow quick restart
socket_set_option($serverSock, SOL_SOCKET, SO_REUSEADDR,1);
if (socket_bind($serverSock, $address, $port) !== false) {
if (socket_listen($serverSock, 5) === false) {
error_log("socket_listen() failed: reason: " . socket_strerror(socket_last_error($serverSock)) . "\n");
exit;
}
//Ok, we finally made it. Broker is in business
socket_set_nonblock($serverSock);
$clientSocks = array();
$noSocks = array();
$allSocks = array($serverSock);
while (true) {
//Wait for any new data or a new connection
$allSocksRead = $allSocks; //Make a copy for read tracking
$allSocksExcept = $allSocks; //Make a copy for error tracking
$delay = NULL;
if(($selectRes = socket_select($allSocksRead, $noSocks, $allSocksExcept, $delay)) === FALSE || $selectRes === 0){
error_log("This too should never happen." . socket_strerror(socket_last_error($serverSock)));
continue;
}
//Handle the errors (disconnected clients, etc.)
foreach($allSocksExcept as $errorSock){
killSock($errorSock, $allSocks); //Kill the zombie socket.
if($errorSock === $serverSock){ //Uh oh! we're dead.
error_log("Server died. " . socket_strerror(socket_last_error($serverSock)));
exit;
}
}
//Handle the new messages or new connections.
foreach($allSocksRead as $readSock){
if($readSock === $serverSock){ // New guests! Welcome. We hope your stay here is excellent.
if (($newCliSock = socket_accept($serverSock)) === false) {
error_log("This shouldn't happen. socket_accept failed: " . socket_strerror(socket_last_error($serverSock)));
}else{
array_push($allSocks, $newCliSock);
}
continue;
}
//New message
$buf = '<no data>';
if(($recvRes = socket_recv($readSock, $buf, $BUFFER_SIZE, MSG_DONTWAIT)) !== FALSE){
foreach($allSocks as $sendSock){
//Find the socket, then relay the message to it. (not back to origin or sendSock though)
if($sendSock !== $serverSock && $sendSock != $readSock && socket_write($sendSock, $buf, $recvRes) === FALSE){
killSock($sendSock, $allSocks); // If it failed, they probably closed their connection. Drop 'em.
}
}
echo "event: text\ndata:$buf\n\n"; //Write it to our client too
ob_flush();
flush();
}
killSock($readSock, $allSocks); // Now close it, because we're done with it. Send socks are 1 time use.
}
}
}else{
socket_close($serverSock);
//Perhaps a chat server is already running. Let's try connecting to it.
//***********************************
//************ Stream ***************
//***********************************
$cliSock = getSock();
if (($result = socket_connect($cliSock, $address, $port)) === false) {
error_log("socket_connect() failed: ($result) " . socket_strerror(socket_last_error($cliSock)) . "\n");
exit;
}
socket_set_nonblock($cliSock);
while(true) {
//Wait for new data
$delay = NULL;
$readArr = array($cliSock);
$noSocks = array();
$errArr = array($cliSock);
if(($selectRes = socket_select($readArr, $noSocks, $errArr, $delay)) === FALSE || $selectRes === 0 || count($errArr) > 0){
error_log("Something is a bit rotten in Denmark: " . socket_strerror(socket_last_error($cliSock)));
break;
}
$buf = '...';
if (false === ($rbytes = socket_recv($cliSock, $buf, $BUFFER_SIZE, 0))) {
error_log("This shouldn't happen, socket_recv failed: " . socket_strerror(socket_last_error($cliSock)));
break;
}
echo "event: text\ndata:$buf\n\n";
ob_flush();
flush();
}
socket_close($cliSock);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment