Advertisement
Guest User

websocket.class.php

a guest
Dec 8th, 2011
421
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 9.26 KB | None | 0 0
  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.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement