Advertisement
Guest User

WebSocket Server

a guest
Nov 18th, 2011
456
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 7.89 KB | None | 0 0
  1. <?php
  2. error_reporting(E_ALL);
  3. set_time_limit(0);
  4. ob_implicit_flush();
  5. date_default_timezone_set('America/Chicago');
  6.  
  7. $master  = WebSocket('192.168.1.29', 12345);
  8. $sockets = array($master);
  9. $users   = array();
  10.  
  11. while(true)
  12. {
  13.     $changed = $sockets;
  14.     socket_select($changed,$write=NULL,$except=NULL,NULL);
  15.     foreach($changed as $socket)
  16.     {
  17.         if($socket == $master)
  18.         {
  19.             $client = socket_accept($master);
  20.             if($client < 0)
  21.             {
  22.                 server_log('socket_accept() failed', 2);
  23.                 continue;
  24.             }
  25.             else
  26.             {
  27.                 connect($client);
  28.             }
  29.         }
  30.         else
  31.         {
  32.             $bytes = socket_recv($socket,$buffer,2048,0);
  33.             if($bytes==0)
  34.             {
  35.                 disconnect($socket);
  36.             }
  37.             else
  38.             {  
  39.                 $user = getuserbysocket($socket);
  40.  
  41.                 if(!$user->handshake)
  42.                 {
  43.                     dohandshake($user,$buffer);
  44.                 }
  45.               else
  46.                {
  47.                 // Decode the WebSocket data and process accordingly
  48.                 $data = hybi10Decode($buffer);
  49.                
  50.                 if($data['type'] == 'text')
  51.                 {
  52.                         process($user, $data['payload']);
  53.                     }
  54.                     elseif($data['type'] == 'ping')
  55.                     {
  56.                        
  57.                     }
  58.                     elseif($data['type'] == 'pong')
  59.                     {
  60.                        
  61.                     }
  62.                     elseif($data['type'] == 'close')
  63.                     {
  64.                        
  65.                     }
  66.                 }
  67.             }
  68.         }
  69.     }
  70. }
  71.  
  72. //---------------------------------------------------------------
  73. function server_log($msg, $sev = 1)
  74. {
  75.     echo '[' . date('G:i:s') . "] $msg\n"; 
  76. }
  77.  
  78. function process($user, $msg)
  79. {
  80.     server_log("Message received: ". $msg);
  81. }
  82.  
  83. function send($client,$msg)
  84. {
  85.     server_log("> ".$msg, 1);
  86.     $msg = hybi10Encode($msg, 'close');
  87.     socket_write($client,$msg,strlen($msg));
  88. }
  89.  
  90. function WebSocket($address,$port)
  91. {
  92.     $master=socket_create(AF_INET, SOCK_STREAM, SOL_TCP)     or die("socket_create() failed");
  93.     socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1)  or die("socket_option() failed");
  94.     socket_bind($master, $address, $port)                    or die("socket_bind() failed");
  95.     socket_listen($master,20)                                or die("socket_listen() failed");
  96.     server_log('Server Started : ' . date('Y-m-d H:i:s'), 1);
  97.     server_log("Master socket  : $master", 1);
  98.     server_log("Listening on   : {$address}:{$port}", 1);
  99.     return $master;
  100. }
  101.  
  102. function connect($socket)
  103. {
  104.     global $sockets,$users;
  105.     $user = new User();
  106.     $user->id = uniqid();
  107.     $user->socket = $socket;
  108.     array_push($users,$user);
  109.     array_push($sockets,$socket);
  110.     server_log('New client connected', 1);
  111. }
  112.  
  113. function disconnect($socket){
  114.   global $sockets,$users;
  115.   $found=null;
  116.   $n=count($users);
  117.   for($i=0;$i<$n;$i++){
  118.     if($users[$i]->socket==$socket){ $found=$i; break; }
  119.   }
  120.   if(!is_null($found)){ array_splice($users,$found,1); }
  121.   $index = array_search($socket,$sockets);
  122.   socket_close($socket);
  123.   console($socket." DISCONNECTED!");
  124.   if($index>=0){ array_splice($sockets,$index,1); }
  125. }
  126.  
  127. function dohandshake($user, $buffer)
  128. {
  129.     server_log('Requesting handshake...', 1);
  130.    
  131.     // Determine which version of the WebSocket protocol the client is using
  132.     if(preg_match("/Sec-WebSocket-Version: (.*)\r\n/ ", $buffer, $match))
  133.         $version = $match[1];
  134.     else
  135.         return false;
  136.        
  137.     if($version == 8)
  138.     {
  139.         // Extract header variables
  140.         if(preg_match("/GET (.*) HTTP/"   ,$buffer,$match)){ $r=$match[1]; }
  141.         if(preg_match("/Host: (.*)\r\n/"  ,$buffer,$match)){ $h=$match[1]; }
  142.         if(preg_match("/Sec-WebSocket-Origin: (.*)\r\n/",$buffer,$match)){ $o=$match[1]; }
  143.         if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/",$buffer,$match)){ $k = $match[1]; }
  144.  
  145.         // Generate our Socket-Accept key based on the IETF specifications
  146.         $accept_key = $k . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
  147.         $accept_key = sha1($accept_key, true);
  148.         $accept_key = base64_encode($accept_key);
  149.        
  150.         $upgrade =  "HTTP/1.1 101 Switching Protocols\r\n" .
  151.                         "Upgrade: websocket\r\n" .
  152.                         "Connection: Upgrade\r\n" .
  153.                         "Sec-WebSocket-Accept: $accept_key\r\n\r\n";
  154.                        
  155.         socket_write($user->socket, $upgrade, strlen($upgrade));
  156.         $user->handshake = true;
  157.         return true;
  158.     }
  159.     else
  160.     {
  161.         server_log("Client is trying to use an unsupported WebSocket protocol ({$version})", 1);
  162.         return false;
  163.     }
  164. }
  165.  
  166. function getuserbysocket($socket)
  167. {
  168.     global $users;
  169.     $found = null;
  170.     foreach($users as $user)
  171.     {
  172.         if($user->socket==$socket)
  173.         {
  174.             $found = $user;
  175.             break;
  176.         }
  177.     }
  178.   return $found;
  179. }
  180.  
  181. function hybi10Encode($payload, $type = 'text', $masked = true)
  182. {
  183.     $frameHead = array();
  184.     $frame = '';
  185.     $payloadLength = strlen($payload);
  186.  
  187.     switch($type)
  188.     {
  189.         case 'text':
  190.         // first byte indicates FIN, Text-Frame (10000001):
  191.         $frameHead[0] = 129;
  192.         break;
  193.  
  194.         case 'close':
  195.         // first byte indicates FIN, Close Frame(10001000):
  196.         $frameHead[0] = 136;
  197.         break;
  198.  
  199.         case 'ping':
  200.         // first byte indicates FIN, Ping frame (10001001):
  201.         $frameHead[0] = 137;
  202.         break;
  203.  
  204.         case 'pong':
  205.         // first byte indicates FIN, Pong frame (10001010):
  206.         $frameHead[0] = 138;
  207.         break;
  208.     }
  209.  
  210.     // set mask and payload length (using 1, 3 or 9 bytes)
  211.     if($payloadLength > 65535)
  212.     {
  213.         $payloadLengthBin = str_split(sprintf('%064b', $payloadLength), 8);
  214.         $frameHead[1] = ($masked === true) ? 255 : 127;
  215.         for($i = 0; $i < 8; $i++)
  216.         {
  217.             $frameHead[$i+2] = bindec($payloadLengthBin[$i]);
  218.         }
  219.  
  220.         // most significant bit MUST be 0 (close connection if frame too big)
  221.         if($frameHead[2] > 127)
  222.         {
  223.             $this->close(1004);
  224.             return false;
  225.         }
  226.     }
  227.     elseif($payloadLength > 125)
  228.     {
  229.         $payloadLengthBin = str_split(sprintf('%016b', $payloadLength), 8);
  230.         $frameHead[1] = ($masked === true) ? 254 : 126;
  231.         $frameHead[2] = bindec($payloadLengthBin[0]);
  232.         $frameHead[3] = bindec($payloadLengthBin[1]);
  233.     }
  234.     else
  235.     {
  236.         $frameHead[1] = ($masked === true) ? $payloadLength + 128 : $payloadLength;
  237.     }
  238.  
  239.     // convert frame-head to string:
  240.     foreach(array_keys($frameHead) as $i)
  241.     {
  242.         $frameHead[$i] = chr($frameHead[$i]);
  243.     }
  244.  
  245.     if($masked === true)
  246.     {
  247.         // generate a random mask:
  248.         $mask = array();
  249.         for($i = 0; $i < 4; $i++)
  250.         {
  251.             $mask[$i] = chr(rand(0, 255));
  252.         }
  253.  
  254.         $frameHead = array_merge($frameHead, $mask);
  255.     }
  256.     $frame = implode('', $frameHead);
  257.  
  258.     // append payload to frame:
  259.     $framePayload = array();
  260.     for($i = 0; $i < $payloadLength; $i++)
  261.     {
  262.         $frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i];
  263.     }
  264.  
  265.     return $frame;
  266. }
  267.  
  268. function hybi10Decode($data)
  269. {
  270.     $payloadLength = '';
  271.     $mask = '';
  272.     $unmaskedPayload = '';
  273.     $decodedData = array();
  274.  
  275.     // estimate frame type:
  276.     $firstByteBinary = sprintf('%08b', ord($data[0]));
  277.     $secondByteBinary = sprintf('%08b', ord($data[1]));
  278.     $opcode = bindec(substr($firstByteBinary, 4, 4));
  279.     $isMasked = ($secondByteBinary[0] == '1') ? true : false;
  280.     $payloadLength = ord($data[1]) & 127;
  281.  
  282.     // close connection if unmasked frame is received:
  283.     if($isMasked === false)
  284.     {
  285.         $this->close(1002);
  286.     }
  287.  
  288.     switch($opcode)
  289.     {
  290.         // text frame:
  291.         case 1:
  292.         $decodedData['type'] = 'text';
  293.         break;
  294.  
  295.         // connection close frame:
  296.         case 8:
  297.         $decodedData['type'] = 'close';
  298.         break;
  299.  
  300.         // ping frame:
  301.         case 9:
  302.         $decodedData['type'] = 'ping';
  303.         break;
  304.  
  305.         // pong frame:
  306.         case 10:
  307.         $decodedData['type'] = 'pong';
  308.         break;
  309.  
  310.         default:
  311.         // Close connection on unknown opcode:
  312.         $this->close(1003);
  313.         break;
  314.     }
  315.  
  316.     if($payloadLength === 126)
  317.     {
  318.         $mask = substr($data, 4, 4);
  319.         $payloadOffset = 8;
  320.     }
  321.     elseif($payloadLength === 127)
  322.     {
  323.         $mask = substr($data, 10, 4);
  324.         $payloadOffset = 14;
  325.     }
  326.     else
  327.     {
  328.         $mask = substr($data, 2, 4);
  329.         $payloadOffset = 6;
  330.     }
  331.  
  332.     $dataLength = strlen($data);
  333.  
  334.     if($isMasked === true)
  335.     {
  336.         for($i = $payloadOffset; $i < $dataLength; $i++)
  337.         {
  338.             $j = $i - $payloadOffset;
  339.             $unmaskedPayload .= $data[$i] ^ $mask[$j % 4];
  340.         }
  341.         $decodedData['payload'] = $unmaskedPayload;
  342.     }
  343.     else
  344.     {
  345.         $payloadOffset = $payloadOffset - 4;
  346.         $decodedData['payload'] = substr($data, $payloadOffset);
  347.     }
  348.  
  349.     return $decodedData;
  350. }
  351.  
  352. class User{
  353.   var $id;
  354.   var $socket;
  355.   var $handshake;
  356. }
  357.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement