PageRenderTime 43ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/includes/classes/class.webSocket.php

https://github.com/victoralex/gameleon
PHP | 303 lines | 203 code | 80 blank | 20 comment | 22 complexity | 5cc4e430cbbad0e3824e8fa2fc689f5f MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. abstract class WebSocket
  3. {
  4. private $masterSocket;
  5. private $sockets = array();
  6. private $users = array();
  7. // Will be overwritten by the expanding class
  8. abstract function processRequest($user,$msg);
  9. abstract function processEnrollment($user);
  10. abstract function init();
  11. function __construct($address,$port)
  12. {
  13. $this->masterSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() failed");
  14. socket_set_option($this->masterSocket, SOL_SOCKET, SO_REUSEADDR, 1) or die("socket_option() failed");
  15. socket_bind($this->masterSocket, $address, $port) or die("socket_bind() failed");
  16. socket_listen($this->masterSocket, 20) or die("socket_listen() failed");
  17. $this->sockets[] = $this->masterSocket;
  18. Debug::addDaemonRow( array(
  19. "description" => "Listening on " . $address . " port " . $port
  20. ) );
  21. Debug::addDaemonRow( array(
  22. "description" => "Master socket " . $this->masterSocket
  23. ) );
  24. }
  25. public function listen()
  26. {
  27. /*
  28. Starting the round-robin socket listener
  29. */
  30. while( true )
  31. {
  32. $changed = $this->sockets;
  33. socket_select($changed, $write=NULL, $except=NULL, NULL);
  34. foreach($changed as $socket)
  35. {
  36. if($socket == $this->masterSocket)
  37. {
  38. // master socket
  39. $client=socket_accept($this->masterSocket);
  40. if($client<0)
  41. {
  42. Debug::addDaemonRow( array(
  43. "description" => "Internal socket_accept() failed."
  44. ) );
  45. continue;
  46. }
  47. $this->connect($client);
  48. continue;
  49. }
  50. // child socket
  51. $bytes = @socket_recv($socket,$buffer,2048,0);
  52. if( $bytes == 0 )
  53. {
  54. // connection closed
  55. $this->processDischargement( $socket );
  56. $this->disconnect($socket);
  57. continue;
  58. }
  59. $user = $this->getuserbysocket($socket);
  60. if(
  61. !$user->handshake &&
  62. $this->dohandshake($user, $buffer)
  63. )
  64. {
  65. // enroll this user
  66. $this->processEnrollment( $user );
  67. continue;
  68. }
  69. // process child socket data
  70. $this->processRequest($user, substr( $buffer, 1, strlen($buffer) - 2 ) );
  71. }
  72. }
  73. }
  74. private function connect($socket)
  75. {
  76. $user = new WebSocketUser();
  77. $user->id = uniqid();
  78. $user->socket = $socket;
  79. $this->users[] = $user;
  80. $this->sockets[] = $socket;
  81. Debug::addDaemonRow( array(
  82. "description" => "User connected. " . $socket
  83. ) );
  84. }
  85. private function disconnect($socket)
  86. {
  87. $n=count($this->users);
  88. for($i=0;$i<$n;$i++)
  89. {
  90. if($this->users[$i]->socket != $socket)
  91. {
  92. continue;
  93. }
  94. array_splice($this->users, $i, 1);
  95. break;
  96. }
  97. socket_close($socket);
  98. $index = array_search( $socket, $this->sockets );
  99. if( $index >= 0 )
  100. {
  101. array_splice($this->sockets, $index, 1);
  102. }
  103. Debug::addDaemonRow( array(
  104. "description" => "User disconnected. " . $socket
  105. ) );
  106. }
  107. private function dohandshake($user, $buffer)
  108. {
  109. list($resource,$host,$origin,$key1,$key2,$l8b,$ck) = $this->getheaders($buffer);
  110. if(
  111. $ck == null ||
  112. !isset( $ck->cookies["PHPSESSID"] ) ||
  113. !file_exists( session_save_path() . DIRECTORY_SEPARATOR . "sess_" . $ck->cookies["PHPSESSID"] ) // session does not exist
  114. )
  115. {
  116. // Headers sent by the user are bad
  117. Debug::addDaemonRow( array(
  118. "description" => "Handshake failed using " . $buffer
  119. ) );
  120. return false;
  121. }
  122. $upgrade = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" .
  123. "Upgrade: WebSocket\r\n" .
  124. "Connection: Upgrade\r\n" .
  125. //"WebSocket-Origin: " . $origin . "\r\n" .
  126. //"WebSocket-Location: ws://" . $host . $resource . "\r\n" .
  127. "Sec-WebSocket-Origin: " . $origin . "\r\n" .
  128. "Sec-WebSocket-Location: ws://" . $host . $resource . "\r\n" .
  129. //"Sec-WebSocket-Protocol: icbmgame\r\n" . //Client doesn't send this
  130. "\r\n" . $this->calcKey($key1,$key2,$l8b);
  131. socket_write($user->socket,$upgrade, strlen($upgrade));
  132. /* get the user's session */
  133. @ session_start();
  134. $user->sessionData = ( session_decode( file_get_contents( session_save_path() . DIRECTORY_SEPARATOR . "sess_" . $ck->cookies["PHPSESSID"] ) ) ? $_SESSION : null );
  135. @ session_destroy();
  136. $user->handshake = true;
  137. Debug::addDaemonRow( array(
  138. "description" => "Done handshaking"
  139. ) );
  140. return true;
  141. }
  142. private function calcKey($key1,$key2,$l8b)
  143. {
  144. //Get the numbers
  145. preg_match_all('/([\d]+)/', $key1, $key1_num);
  146. preg_match_all('/([\d]+)/', $key2, $key2_num);
  147. //Number crunching [/bad pun]
  148. $key1_num = implode($key1_num[0]);
  149. $key2_num = implode($key2_num[0]);
  150. //Count spaces
  151. preg_match_all('/([ ]+)/', $key1, $key1_spc);
  152. preg_match_all('/([ ]+)/', $key2, $key2_spc);
  153. //How many spaces did it find?
  154. $key1_spc = strlen(implode($key1_spc[0]));
  155. $key2_spc = strlen(implode($key2_spc[0]));
  156. if($key1_spc==0|$key2_spc==0)
  157. {
  158. Debug::addDaemonRow( array(
  159. "description" => "Invalid key"
  160. ) );
  161. return;
  162. }
  163. //Some math
  164. $key1_sec = pack("N",$key1_num / $key1_spc); //Get the 32bit secret key, minus the other thing
  165. $key2_sec = pack("N",$key2_num / $key2_spc);
  166. //This needs checking, I'm not completely sure it should be a binary string
  167. return md5($key1_sec.$key2_sec.$l8b,1); //The result, I think
  168. }
  169. private function getheaders($req)
  170. {
  171. $r=$h=$o=null;
  172. if(preg_match("/GET (.*) HTTP/" ,$req,$match)){ $r=$match[1]; }
  173. if(preg_match("/Host: (.*)\r\n/" ,$req,$match)){ $h=$match[1]; }
  174. if(preg_match("/Origin: (.*)\r\n/" ,$req,$match)){ $o=$match[1]; }
  175. if(preg_match("/Cookie: (.*)\r\n/" ,$req,$match))
  176. {
  177. @ $ck = isset( $match[1] ) ? http_parse_cookie( $match[1] ) : null;
  178. }
  179. if(preg_match("/Sec-WebSocket-Key1: (.*)\r\n/",$req,$match))
  180. {
  181. $sk1 = $match[1];
  182. }
  183. if(preg_match("/Sec-WebSocket-Key2: (.*)\r\n/",$req,$match))
  184. {
  185. $sk2 = $match[1];
  186. }
  187. if($match=substr($req,-8))
  188. {
  189. $l8b = $match;
  190. }
  191. return array($r,$h,$o,$sk1,$sk2,$l8b,$ck);
  192. }
  193. protected function getuserbysocket($socket)
  194. {
  195. foreach($this->users as $user)
  196. {
  197. if( $user->socket != $socket )
  198. {
  199. continue;
  200. }
  201. return $user;
  202. }
  203. return null;
  204. }
  205. }
  206. class WebSocketUser
  207. {
  208. public $id;
  209. public $socket;
  210. public $handshake;
  211. public $sessionData = null;
  212. public $characterData = null;
  213. public $lastUpdate = 0;
  214. function send( $msg )
  215. {
  216. Debug::addDaemonRow( array(
  217. "description" => "Sent '" . $msg . "'"
  218. ) );
  219. $msg = chr(0) . $msg . chr(255);
  220. socket_write($this->socket, $msg, strlen($msg));
  221. }
  222. }
  223. ?>