This week only. Pastebin PRO Accounts Christmas Special! Don't miss out!Want more features on Pastebin? Sign Up, it's FREE!
Guest

websocket.class.php

By: a guest on Dec 8th, 2011  |  syntax: PHP  |  size: 9.26 KB  |  views: 128  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. <?php
  2.  
  3. /**
  4.  * Simple implementation of HTML5 WebSocket server-side.
  5.  *
  6.  * PHP versions 5
  7.  *
  8.  * This program is free software: you can redistribute it and/or modify
  9.  * it under the terms of the GNU General Public License as published by
  10.  * the Free Software Foundation, either version 3 of the License, or
  11.  * (at your option) any later version.
  12.  *
  13.  * This program is distributed in the hope that it will be useful,
  14.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16.  * GNU General Public License for more details.
  17.  *
  18.  * You should have received a copy of the GNU General Public License
  19.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  20.  *
  21.  * @package    WebSocket
  22.  * @author     George Nava <georgenava@gmail.com>
  23.  * @author     Vincenzo Ferrari <wilk3ert@gmail.com>
  24.  * @copyright  2010-2011
  25.  * @license    http://www.gnu.org/licenses/gpl.txt GNU GPLv3
  26.  * @version    1.1.0
  27.  * @link       http://code.google.com/p/phpwebsocket/
  28.  */
  29.        
  30.         /**
  31.          * @usage $master = new WebSocket ("localhost", 12345);
  32.          */
  33.         class WebSocket {
  34.                 var $master;
  35.                 var $sockets = array ();
  36.                 var $users = array ();
  37.                 // true to debug
  38.                 var $debug = false;
  39.                 // frame mask
  40.                 var $masks;
  41.                 // initial frames
  42.                 var $initFrame;
  43.  
  44.                 function __construct ($address, $port) {
  45.                         error_reporting (E_ALL);
  46.                         set_time_limit (0);
  47.                         ob_implicit_flush ();
  48.                        
  49.                         // Socket creation
  50.                         $this->master = socket_create (AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() failed");
  51.                         socket_set_option ($this->master, SOL_SOCKET, SO_REUSEADDR, 1) or die("socket_option() failed");
  52.                         socket_bind ($this->master, $address, $port) or die("socket_bind() failed");
  53.                         socket_listen ($this->master, 20) or die("socket_listen() failed");
  54.                         $this->sockets[] = $this->master;
  55.                         $this->say ("Server Started : " . date ('Y-m-d H:i:s'));
  56.                         $this->say ("Listening on   : {$address} {$port}");
  57.                         $this->say ("Master socket  : {$this->master}\n");
  58.                        
  59.                         // Main loop
  60.                         while (true) {
  61.                                 $changed = $this->sockets;
  62.                                 socket_select ($changed, $write = NULL, $except = NULL, NULL);
  63.                                
  64.                                 foreach ($changed as $socket) {
  65.                                         if ($socket == $this->master) {
  66.                                                 $client = socket_accept ($this->master);
  67.                                                
  68.                                                 if ($client < 0) {
  69.                                                         $this->log ("socket_accept() failed");
  70.                                                         continue;
  71.                                                 }
  72.                                                 else {
  73.                                                         // Connects the socket
  74.                                                         $this->connect ($client);
  75.                                                 }
  76.                                         }
  77.                                         else {
  78.                                                 $bytes = @socket_recv ($socket, $buffer, 2048, 0);
  79.                                                 if ($bytes == 0) {
  80.                                                         // On socket.close ();
  81.                                                         $this->disconnect ($socket);
  82.                                                 }
  83.                                                 else {
  84.                                                         // Retrieve the user from his socket
  85.                                                         $user = $this->getuserbysocket ($socket);
  86.                                                        
  87.                                                         if (!$user->handshake) {
  88.                                                                 $this->dohandshake ($user, $buffer);
  89.                                                         }
  90.                                                         else {
  91.                                                                 $this->process ($user, $this->decode ($buffer));
  92.                                                         }
  93.                                                 }
  94.                                         }
  95.                                 }
  96.                         }
  97.                 }
  98.  
  99.                 /**
  100.                  * @brief Echo incoming messages back to the client
  101.                  * @note Extend and modify this method to suit your needs
  102.                  * @param $user {User} : owner of the message
  103.                  * @param $msg {String} : the message to echo
  104.                  * @return void
  105.                  */
  106.                 function process ($user, $msg) {
  107.                         $this->send ($user->socket, $msg);
  108.                 }
  109.  
  110.                 /**
  111.                  * @brief Send a message to a client
  112.                  * @param $client {Socket} : socket to send the message
  113.                  * @param $msg {String} : the message to send
  114.                  * @return void
  115.                  */
  116.                 function send ($client, $send_msg) {
  117.                         $this->say ("> {$send_msg}");
  118.                         $send_msg = $this->encode ($send_msg);
  119.                         socket_write ($client, $send_msg, strlen ($send_msg));
  120.                 }
  121.  
  122.                 /**
  123.                  * @brief Connect a new client (socket)
  124.                  * @param $socket {Socket} : socket to connect
  125.                  * @return void
  126.                  */
  127.                 function connect ($socket) {
  128.                         $user = new User ();
  129.                         $user->id = uniqid ();
  130.                         $user->socket = $socket;
  131.                        
  132.                         array_push ($this->users, $user);
  133.                         array_push ($this->sockets, $socket);
  134.                        
  135.                         $this->log ("{$socket} CONNECTED!");
  136.                         $this->log (date ("d/n/Y ") . "at " . date ("H:i:s T"));
  137.                 }
  138.  
  139.                 /**
  140.                  * @brief Disconnect a client (socket)
  141.                  * @param $socket {Socket} : socket to disconnect
  142.                  * @return void
  143.                  */
  144.                 function disconnect ($socket) {
  145.                         $found = null;
  146.                         $n = count ($this->users);
  147.                        
  148.                         // Finds the right user index from the given socket
  149.                         for ($i = 0; $i < $n; $i++) {
  150.                                 if ($this->users[$i]->socket == $socket) {
  151.                                         $found = $i;
  152.                                         break;
  153.                                 }
  154.                         }
  155.                        
  156.                         if (!is_null ($found)) {
  157.                                 array_splice ($this->users, $found, 1);
  158.                         }
  159.                        
  160.                         $index = array_search ($socket, $this->sockets);
  161.                         socket_close ($socket);
  162.                         $this->log ("{$socket} DISCONNECTED!");
  163.                        
  164.                         if ($index >= 0) {
  165.                                 array_splice ($this->sockets, $index, 1);
  166.                         }
  167.                 }
  168.  
  169.                 /**
  170.                  * @brief Do the handshake between server and client
  171.                  * @param $user {User} : user to handshake
  172.                  * @param $buffer {String} : user's request
  173.                  * @return Boolean
  174.                  */
  175.                 function dohandshake ($user, $buffer) {
  176.                         $this->log ("\nRequesting handshake...");
  177.                         $this->log ($buffer);
  178.                        
  179.                         list ($resource, $host, $connection, $version, $origin, $key, $upgrade) = $this->getheaders ($buffer);
  180.                        
  181.                         $this->log ("Handshaking...");
  182.                         $reply  =
  183.                                 "HTTP/1.1 101 Switching Protocols\r\n" .
  184.                                 "Upgrade: {$upgrade}\r\n" .
  185.                                 "Connection: {$connection}\r\n" .
  186.                                 "Sec-WebSocket-Version: {$version}\r\n" .
  187.                                 "Sec-WebSocket-Origin: {$origin}\r\n" .
  188.                                 "Sec-WebSocket-Location: ws://{$host}{$resource}\r\n" .
  189.                                 "Sec-WebSocket-Accept: " . $this->calcKey ($key) . "\r\n" .
  190.                                 "\r\n";
  191.                        
  192.                         // Closes the handshake
  193.                         socket_write ($user->socket, $reply, strlen ($reply));
  194.                        
  195.                         $user->handshake = true;
  196.                         $this->log ($reply);
  197.                         $this->log ("Done handshaking...");
  198.                        
  199.                         return true;
  200.                 }
  201.  
  202.                 /**
  203.                  * @brief Calculate Sec-WebSocket-Accept
  204.                  * @note For more info look at: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
  205.                  * @param $key {String} : key to calculate
  206.                  * @return Calculated key
  207.                  */
  208.                 function calcKey ($key) {
  209.                         // Constant string as specified in the ietf-hybi-17 draft
  210.                         $key .= "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
  211.                         $key = sha1 ($key);
  212.                         $key = pack ('H*', $key);
  213.                         $key = base64_encode ($key);
  214.                        
  215.                         return $key;
  216.                 }
  217.  
  218.                 /**
  219.                  * @brief Get the client request headers
  220.                  * @param $buffer {String} : buffer from which to draw the headers.
  221.                  * @return Array
  222.                  */
  223.                 function getheaders ($buffer) {
  224.                         $resource = $host = $connection = $version = $origin = $key = $upgrade = null;
  225.                        
  226.                         preg_match ('#GET (.*?) HTTP#', $buffer, $match) && $resource = $match[1];
  227.                         preg_match ("#Host: (.*?)\r\n#", $buffer, $match) && $host = $match[1];
  228.                         preg_match ("#Connection: (.*?)\r\n#", $buffer, $match) && $connection = $match[1];
  229.                         preg_match ("#Sec-WebSocket-Version: (.*?)\r\n#", $buffer, $match) && $version = $match[1];
  230.                         preg_match ("#Sec-WebSocket-Origin: (.*?)\r\n#", $buffer, $match) && $origin = $match[1];
  231.                         preg_match ("#Sec-WebSocket-Key: (.*?)\r\n#", $buffer, $match) && $key = $match[1];
  232.                         preg_match ("#Upgrade: (.*?)\r\n#", $buffer, $match) && $upgrade = $match[1];
  233.                        
  234.                         return array ($resource, $host, $connection, $version, $origin, $key, $upgrade);
  235.                 }
  236.  
  237.                 /**
  238.                  * @brief Retrieve an user from his socket
  239.                  * @param $socket {Socket} : socket of the user to search
  240.                  * @return User or null
  241.                  */
  242.                 function getuserbysocket ($socket) {
  243.                         $found = null;
  244.                        
  245.                         foreach ($this->users as $user) {
  246.                                 if ($user->socket == $socket) {
  247.                                         $found = $user;
  248.                                         break;
  249.                                 }
  250.                         }
  251.                        
  252.                         return $found;
  253.                 }
  254.                
  255.                 /**
  256.                  * @brief Decode messages as specified in the ietf-hybi-17 draft
  257.                  * @param $msg {String} : message to decode
  258.                  * @return Message decoded
  259.                  */
  260.                 function decode ($msg) {
  261.                         $len = $data = $decoded = $index = null;
  262.                         $len = $msg[1] & 127;
  263.                
  264.                         if ($len === 126) {
  265.                                 $this->masks = substr ($msg, 4, 4);
  266.                                 $data = substr ($msg, 8);
  267.                                 $this->initFrame = substr ($msg, 0, 4);
  268.                         }
  269.                         else if ($len === 127) {
  270.                                 $this->masks = substr ($msg, 10, 4);
  271.                                 $data = substr ($msg, 14);
  272.                                 $this->initFrame = substr ($msg, 0, 10);
  273.                         }
  274.                         else {
  275.                                 $this->masks = substr ($msg, 2, 4);
  276.                                 $data = substr ($msg, 6);
  277.                                 $this->initFrame = substr ($msg, 0, 2);
  278.                         }
  279.                
  280.                         for ($index = 0; $index < strlen ($data); $index++) {
  281.                                 $decoded .= $data[$index] ^ $this->masks[$index % 4];
  282.                         }
  283.                
  284.                         return $decoded;
  285.                 }
  286.        
  287.                 /**
  288.                  * @brief Encode messages
  289.                  * @param $msg {String} : message to encode
  290.                  * @return Message encoded
  291.                  */
  292.                 function encode ($send_msg) {
  293.                         $index = $encoded = null;
  294.                
  295.                         for ($index = 0; $index < strlen ($send_msg); $index++) {
  296.                                 $encoded .= $send_msg[$index] ^ $this->masks[$index % 4];
  297.                         }
  298.                
  299.                         $encoded = $this->initFrame . $this->masks . $encoded;
  300.                
  301.                         return $encoded;
  302.                 }
  303.  
  304.                 /**
  305.                  * @brief Local echo messages
  306.                  * @param $msg {String} : message to echo
  307.                  * @return void
  308.                  */
  309.                 function say ($msg = "") {
  310.                         echo "{$msg}\n";
  311.                 }
  312.                
  313.                 /**
  314.                  * @brief Log function
  315.                  * @param $msg {String} : message to log
  316.                  * @return void
  317.                  */
  318.                 function log ($msg = "") {
  319.                         if ($this->debug) {
  320.                                 echo "{$msg}\n";
  321.                         }
  322.                 }
  323.         }
  324.  
  325.         class User {
  326.                 var $id;
  327.                 var $socket;
  328.                 var $handshake;
  329.         }
  330. ?>
  331.  
  332.  
clone this paste RAW Paste Data