.
*
* @package WebSocket
* @author George Nava
* @author Vincenzo Ferrari
* @copyright 2010-2011
* @license http://www.gnu.org/licenses/gpl.txt GNU GPLv3
* @version 1.1.0
* @link http://code.google.com/p/phpwebsocket/
*/
/**
* @usage $master = new WebSocket ("localhost", 12345);
*/
class WebSocket {
var $master;
var $sockets = array ();
var $users = array ();
// true to debug
var $debug = false;
// frame mask
var $masks;
// initial frames
var $initFrame;
function __construct ($address, $port) {
error_reporting (E_ALL);
set_time_limit (0);
ob_implicit_flush ();
// Socket creation
$this->master = socket_create (AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() failed");
socket_set_option ($this->master, SOL_SOCKET, SO_REUSEADDR, 1) or die("socket_option() failed");
socket_bind ($this->master, $address, $port) or die("socket_bind() failed");
socket_listen ($this->master, 20) or die("socket_listen() failed");
$this->sockets[] = $this->master;
$this->say ("Server Started : " . date ('Y-m-d H:i:s'));
$this->say ("Listening on : {$address} {$port}");
$this->say ("Master socket : {$this->master}\n");
// Main loop
while (true) {
$changed = $this->sockets;
socket_select ($changed, $write = NULL, $except = NULL, NULL);
foreach ($changed as $socket) {
if ($socket == $this->master) {
$client = socket_accept ($this->master);
if ($client < 0) {
$this->log ("socket_accept() failed");
continue;
}
else {
// Connects the socket
$this->connect ($client);
}
}
else {
$bytes = @socket_recv ($socket, $buffer, 2048, 0);
if ($bytes == 0) {
// On socket.close ();
$this->disconnect ($socket);
}
else {
// Retrieve the user from his socket
$user = $this->getuserbysocket ($socket);
if (!$user->handshake) {
$this->dohandshake ($user, $buffer);
}
else {
$this->process ($user, $this->decode ($buffer));
}
}
}
}
}
}
/**
* @brief Echo incoming messages back to the client
* @note Extend and modify this method to suit your needs
* @param $user {User} : owner of the message
* @param $msg {String} : the message to echo
* @return void
*/
function process ($user, $msg) {
$this->send ($user->socket, $msg);
}
/**
* @brief Send a message to a client
* @param $client {Socket} : socket to send the message
* @param $msg {String} : the message to send
* @return void
*/
function send ($client, $send_msg) {
$this->say ("> {$send_msg}");
$send_msg = $this->encode ($send_msg);
socket_write ($client, $send_msg, strlen ($send_msg));
}
/**
* @brief Connect a new client (socket)
* @param $socket {Socket} : socket to connect
* @return void
*/
function connect ($socket) {
$user = new User ();
$user->id = uniqid ();
$user->socket = $socket;
array_push ($this->users, $user);
array_push ($this->sockets, $socket);
$this->log ("{$socket} CONNECTED!");
$this->log (date ("d/n/Y ") . "at " . date ("H:i:s T"));
}
/**
* @brief Disconnect a client (socket)
* @param $socket {Socket} : socket to disconnect
* @return void
*/
function disconnect ($socket) {
$found = null;
$n = count ($this->users);
// Finds the right user index from the given socket
for ($i = 0; $i < $n; $i++) {
if ($this->users[$i]->socket == $socket) {
$found = $i;
break;
}
}
if (!is_null ($found)) {
array_splice ($this->users, $found, 1);
}
$index = array_search ($socket, $this->sockets);
socket_close ($socket);
$this->log ("{$socket} DISCONNECTED!");
if ($index >= 0) {
array_splice ($this->sockets, $index, 1);
}
}
/**
* @brief Do the handshake between server and client
* @param $user {User} : user to handshake
* @param $buffer {String} : user's request
* @return Boolean
*/
function dohandshake ($user, $buffer) {
$this->log ("\nRequesting handshake...");
$this->log ($buffer);
list ($resource, $host, $connection, $version, $origin, $key, $upgrade) = $this->getheaders ($buffer);
$this->log ("Handshaking...");
$reply =
"HTTP/1.1 101 Switching Protocols\r\n" .
"Upgrade: {$upgrade}\r\n" .
"Connection: {$connection}\r\n" .
"Sec-WebSocket-Version: {$version}\r\n" .
"Sec-WebSocket-Origin: {$origin}\r\n" .
"Sec-WebSocket-Location: ws://{$host}{$resource}\r\n" .
"Sec-WebSocket-Accept: " . $this->calcKey ($key) . "\r\n" .
"\r\n";
// Closes the handshake
socket_write ($user->socket, $reply, strlen ($reply));
$user->handshake = true;
$this->log ($reply);
$this->log ("Done handshaking...");
return true;
}
/**
* @brief Calculate Sec-WebSocket-Accept
* @note For more info look at: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
* @param $key {String} : key to calculate
* @return Calculated key
*/
function calcKey ($key) {
// Constant string as specified in the ietf-hybi-17 draft
$key .= "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
$key = sha1 ($key);
$key = pack ('H*', $key);
$key = base64_encode ($key);
return $key;
}
/**
* @brief Get the client request headers
* @param $buffer {String} : buffer from which to draw the headers.
* @return Array
*/
function getheaders ($buffer) {
$resource = $host = $connection = $version = $origin = $key = $upgrade = null;
preg_match ('#GET (.*?) HTTP#', $buffer, $match) && $resource = $match[1];
preg_match ("#Host: (.*?)\r\n#", $buffer, $match) && $host = $match[1];
preg_match ("#Connection: (.*?)\r\n#", $buffer, $match) && $connection = $match[1];
preg_match ("#Sec-WebSocket-Version: (.*?)\r\n#", $buffer, $match) && $version = $match[1];
preg_match ("#Sec-WebSocket-Origin: (.*?)\r\n#", $buffer, $match) && $origin = $match[1];
preg_match ("#Sec-WebSocket-Key: (.*?)\r\n#", $buffer, $match) && $key = $match[1];
preg_match ("#Upgrade: (.*?)\r\n#", $buffer, $match) && $upgrade = $match[1];
return array ($resource, $host, $connection, $version, $origin, $key, $upgrade);
}
/**
* @brief Retrieve an user from his socket
* @param $socket {Socket} : socket of the user to search
* @return User or null
*/
function getuserbysocket ($socket) {
$found = null;
foreach ($this->users as $user) {
if ($user->socket == $socket) {
$found = $user;
break;
}
}
return $found;
}
/**
* @brief Decode messages as specified in the ietf-hybi-17 draft
* @param $msg {String} : message to decode
* @return Message decoded
*/
function decode ($msg) {
$len = $data = $decoded = $index = null;
$len = $msg[1] & 127;
if ($len === 126) {
$this->masks = substr ($msg, 4, 4);
$data = substr ($msg, 8);
$this->initFrame = substr ($msg, 0, 4);
}
else if ($len === 127) {
$this->masks = substr ($msg, 10, 4);
$data = substr ($msg, 14);
$this->initFrame = substr ($msg, 0, 10);
}
else {
$this->masks = substr ($msg, 2, 4);
$data = substr ($msg, 6);
$this->initFrame = substr ($msg, 0, 2);
}
for ($index = 0; $index < strlen ($data); $index++) {
$decoded .= $data[$index] ^ $this->masks[$index % 4];
}
return $decoded;
}
/**
* @brief Encode messages
* @param $msg {String} : message to encode
* @return Message encoded
*/
function encode ($send_msg) {
$index = $encoded = null;
for ($index = 0; $index < strlen ($send_msg); $index++) {
$encoded .= $send_msg[$index] ^ $this->masks[$index % 4];
}
$encoded = $this->initFrame . $this->masks . $encoded;
return $encoded;
}
/**
* @brief Local echo messages
* @param $msg {String} : message to echo
* @return void
*/
function say ($msg = "") {
echo "{$msg}\n";
}
/**
* @brief Log function
* @param $msg {String} : message to log
* @return void
*/
function log ($msg = "") {
if ($this->debug) {
echo "{$msg}\n";
}
}
}
class User {
var $id;
var $socket;
var $handshake;
}
?>