GameQ.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900
  1. <?php
  2. /**
  3. * This file is part of GameQ.
  4. *
  5. * GameQ is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU Lesser General Public License as published by
  7. * the Free Software Foundation; either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * GameQ is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Lesser General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /*
  19. * Init some stuff
  20. */
  21. // Figure out where we are so we can set the proper references
  22. define('GAMEQ_BASE', realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR);
  23. // Define the autoload so we can require files easy
  24. spl_autoload_register(array('GameQ', 'auto_load'));
  25. /**
  26. * Base GameQ Class
  27. *
  28. * This class should be the only one that is included when you use GameQ to query
  29. * any games servers. All necessary sub-classes are loaded as needed.
  30. *
  31. * Requirements: See wiki or README for more information on the requirements
  32. * - PHP 5.2+ (Recommended 5.3+)
  33. * * Bzip2 - http://www.php.net/manual/en/book.bzip2.php
  34. * * Zlib - http://www.php.net/manual/en/book.zlib.php
  35. *
  36. * @author Austin Bischoff <[email protected]>
  37. */
  38. class GameQ
  39. {
  40. /*
  41. * Constants
  42. */
  43. const VERSION = '2.0.4';
  44. /*
  45. * Server array keys
  46. */
  47. const SERVER_TYPE = 'type';
  48. const SERVER_HOST = 'host';
  49. const SERVER_ID = 'id';
  50. const SERVER_OPTIONS = 'options';
  51. /* Static Section */
  52. protected static $instance = NULL;
  53. /**
  54. * Create a new instance of this class
  55. */
  56. public static function factory()
  57. {
  58. // Create a new instance
  59. self::$instance = new self();
  60. // Return this new instance
  61. return self::$instance;
  62. }
  63. /**
  64. * Attempt to auto-load a class based on the name
  65. *
  66. * @param string $class
  67. * @throws GameQException
  68. */
  69. public static function auto_load($class)
  70. {
  71. try
  72. {
  73. // Transform the class name into a path
  74. $file = str_replace('_', '/', strtolower($class));
  75. // Find the file and return the full path, if it exists
  76. if ($path = self::find_file($file))
  77. {
  78. // Load the class file
  79. require $path;
  80. // Class has been found
  81. return TRUE;
  82. }
  83. // Class is not in the filesystem
  84. return FALSE;
  85. }
  86. catch (Exception $e)
  87. {
  88. throw new GameQException($e->getMessage(), $e->getCode(), $e);
  89. die;
  90. }
  91. }
  92. /**
  93. * Try to find the file based on the class passed.
  94. *
  95. * @param string $file
  96. */
  97. public static function find_file($file)
  98. {
  99. $found = FALSE; // By default we did not find anything
  100. // Create a partial path of the filename
  101. $path = GAMEQ_BASE.$file.'.php';
  102. // Is a file so we can include it
  103. if(is_file($path))
  104. {
  105. $found = $path;
  106. }
  107. return $found;
  108. }
  109. /* Dynamic Section */
  110. /**
  111. * Defined options by default
  112. *
  113. * @var array()
  114. */
  115. protected $options = array(
  116. 'debug' => FALSE,
  117. 'timeout' => 3, // Seconds
  118. 'filters' => array(),
  119. // Advanced settings
  120. 'stream_timeout' => 200000, // See http://www.php.net/manual/en/function.stream-select.php for more info
  121. 'write_wait' => 500, // How long (in micro-seconds) to pause between writting to server sockets, helps cpu usage
  122. );
  123. /**
  124. * Array of servers being queried
  125. *
  126. * @var array
  127. */
  128. protected $servers = array();
  129. /**
  130. * Holds the list of active sockets. This array is automaically cleaned as needed
  131. *
  132. * @var array
  133. */
  134. protected $sockets = array();
  135. /**
  136. * Make new class and check for requirements
  137. *
  138. * @throws GameQException
  139. * @return boolean
  140. */
  141. public function __construct()
  142. {
  143. // @todo: Add PHP version check?
  144. }
  145. /**
  146. * Get an option's value
  147. *
  148. * @param string $option
  149. */
  150. public function __get($option)
  151. {
  152. return isset($this->options[$option]) ? $this->options[$option] : NULL;
  153. }
  154. /**
  155. * Set an option's value
  156. *
  157. * @param string $option
  158. * @param mixed $value
  159. * @return boolean
  160. */
  161. public function __set($option, $value)
  162. {
  163. $this->options[$option] = $value;
  164. return TRUE;
  165. }
  166. /**
  167. * Chainable call to __set, uses set as the actual setter
  168. *
  169. * @param string $var
  170. * @param mixed $value
  171. * @return GameQ
  172. */
  173. public function setOption($var, $value)
  174. {
  175. // Use magic
  176. $this->{$var} = $value;
  177. return $this; // Make chainable
  178. }
  179. /**
  180. * Set an output filter.
  181. *
  182. * @param string $name
  183. * @param array $params
  184. * @return GameQ
  185. */
  186. public function setFilter($name, $params = array())
  187. {
  188. // Create the proper filter class name
  189. $filter_class = 'GameQ_Filters_'.$name;
  190. try
  191. {
  192. // Pass any parameters and make the class
  193. $this->options['filters'][$name] = new $filter_class($params);
  194. }
  195. catch (GameQ_FiltersException $e)
  196. {
  197. // We catch the exception here, thus the filter is not applied
  198. // but we issue a warning
  199. error_log($e->getMessage(), E_USER_WARNING);
  200. }
  201. return $this; // Make chainable
  202. }
  203. /**
  204. * Remove a global output filter.
  205. *
  206. * @param string $name
  207. * @return GameQ
  208. */
  209. public function removeFilter($name)
  210. {
  211. unset($this->options['filters'][$name]);
  212. return $this; // Make chainable
  213. }
  214. /**
  215. * Add a server to be queried
  216. *
  217. * Example:
  218. * $this->addServer(array(
  219. * // Required keys
  220. * 'type' => 'cs',
  221. * 'host' => '127.0.0.1:27015', '127.0.0.1' or 'somehost.com:27015'
  222. * Port not required, but will use the default port in the class which may not be correct for the
  223. * specific server being queried.
  224. *
  225. * // Optional keys
  226. * 'id' => 'someServerId', // By default will use pased host info (i.e. 127.0.0.1:27015)
  227. * 'options' => array('timeout' => 5), // By default will use global options
  228. * ));
  229. *
  230. * @param array $server_info
  231. * @throws GameQException
  232. * @return boolean|GameQ
  233. */
  234. public function addServer(Array $server_info=NULL)
  235. {
  236. // Check for server type
  237. if(!key_exists(self::SERVER_TYPE, $server_info) || empty($server_info[self::SERVER_TYPE]))
  238. {
  239. throw new GameQException("Missing server info key '".self::SERVER_TYPE."'");
  240. return FALSE;
  241. }
  242. // Check for server host
  243. if(!key_exists(self::SERVER_HOST, $server_info) || empty($server_info[self::SERVER_HOST]))
  244. {
  245. throw new GameQException("Missing server info key '".self::SERVER_HOST."'");
  246. return FALSE;
  247. }
  248. // Check for server id
  249. if(!key_exists(self::SERVER_ID, $server_info) || empty($server_info[self::SERVER_ID]))
  250. {
  251. // Make an id so each server has an id when returned
  252. $server_info[self::SERVER_ID] = $server_info[self::SERVER_HOST];
  253. }
  254. // Check for options
  255. if(!key_exists(self::SERVER_OPTIONS, $server_info)
  256. || !is_array($server_info[self::SERVER_OPTIONS])
  257. || empty($server_info[self::SERVER_OPTIONS]))
  258. {
  259. // Default the options to an empty array
  260. $server_info[self::SERVER_OPTIONS] = array();
  261. }
  262. // Define these
  263. $server_id = $server_info[self::SERVER_ID];
  264. $server_ip = '127.0.0.1';
  265. $server_port = FALSE;
  266. // We have an IPv6 address (and maybe a port)
  267. if(substr_count($server_info[self::SERVER_HOST], ':') > 1)
  268. {
  269. // See if we have a port, input should be in the format [::1]:27015 or similar
  270. if(strstr($server_info[self::SERVER_HOST], ']:'))
  271. {
  272. // Explode to get port
  273. $server_addr = explode(':', $server_info[self::SERVER_HOST]);
  274. // Port is the last item in the array, remove it and save
  275. $server_port = array_pop($server_addr);
  276. // The rest is the address, recombine
  277. $server_ip = implode(':', $server_addr);
  278. unset($server_addr);
  279. }
  280. // Just the IPv6 address, no port defined
  281. else
  282. {
  283. $server_ip = $server_info[self::SERVER_HOST];
  284. }
  285. // Now let's validate the IPv6 value sent, remove the square brackets ([]) first
  286. if(!filter_var(trim($server_ip, '[]'), FILTER_VALIDATE_IP, array(
  287. 'flags' => FILTER_FLAG_IPV6,
  288. )))
  289. {
  290. throw new GameQException("The IPv6 address '{$server_ip}' is invalid.");
  291. return FALSE;
  292. }
  293. }
  294. // IPv4
  295. else
  296. {
  297. // We have a port defined
  298. if(strstr($server_info[self::SERVER_HOST], ':'))
  299. {
  300. list($server_ip, $server_port) = explode(':', $server_info[self::SERVER_HOST]);
  301. }
  302. // No port, just IPv4
  303. else
  304. {
  305. $server_ip = $server_info[self::SERVER_HOST];
  306. }
  307. // Validate the IPv4 value, if FALSE is not a valid IP, maybe a hostname. Try to resolve
  308. if(!filter_var($server_ip, FILTER_VALIDATE_IP, array(
  309. 'flags' => FILTER_FLAG_IPV4,
  310. )))
  311. {
  312. // When gethostbyname() fails it returns the original string
  313. // so if ip and the result from gethostbyname() are equal this failed.
  314. if($server_ip === gethostbyname($server_ip))
  315. {
  316. throw new GameQException("The host '{$server_ip}' is unresolvable to an IP address.");
  317. return FALSE;
  318. }
  319. }
  320. }
  321. // Create the class so we can reference it properly later
  322. $protocol_class = 'GameQ_Protocols_'.ucfirst($server_info[self::SERVER_TYPE]);
  323. // Create the new instance and add it to the servers list
  324. $this->servers[$server_id] = new $protocol_class(
  325. $server_ip,
  326. $server_port,
  327. array_merge($this->options, $server_info[self::SERVER_OPTIONS])
  328. );
  329. return $this; // Make calls chainable
  330. }
  331. /**
  332. * Add multiple servers at once
  333. *
  334. * @param array $servers
  335. * @return GameQ
  336. */
  337. public function addServers(Array $servers=NULL)
  338. {
  339. // Loop thru all the servers and add them
  340. foreach($servers AS $server_info)
  341. {
  342. $this->addServer($server_info);
  343. }
  344. return $this; // Make calls chainable
  345. }
  346. /**
  347. * Clear all the added servers. Creates clean instance.
  348. *
  349. * @return GameQ
  350. */
  351. public function clearServers()
  352. {
  353. // Reset all the servers
  354. $this->servers = array();
  355. $this->sockets = array();
  356. return $this; // Make Chainable
  357. }
  358. /**
  359. * Make all the data requests (i.e. challenges, queries, etc...)
  360. *
  361. * @return multitype:Ambigous <multitype:, multitype:boolean string mixed >
  362. */
  363. public function requestData()
  364. {
  365. // Data returned array
  366. $data = array();
  367. global $settings;
  368. if (isset($settings['remote_query']) and $settings['remote_query'] == 1)
  369. {
  370. global $db;
  371. foreach($this->servers AS $server_id => $instance)
  372. {
  373. $home_info = $db->getGameHomeByIP($instance->ip(), $instance->port_client());
  374. if(is_array($home_info) && !empty($home_info))
  375. {
  376. require_once('includes/lib_remote.php');
  377. $remote = new OGPRemoteLibrary($home_info['agent_ip'], $home_info['agent_port'], $home_info['encryption_key'], $home_info['timeout']);
  378. $return = $remote->remote_query('gameq', $instance->name(), $instance->ip(), $instance->port_client(), $instance->port(), $instance->port_client());
  379. if($return == NULL) continue;
  380. if (preg_match("/_SGAMEQF_(.*)_SGAMEQF_/U", $return, $match))
  381. $data[$server_id] = unserialize($match[1]);
  382. else
  383. continue;
  384. }
  385. }
  386. return $data;
  387. }
  388. else
  389. {
  390. // Init the query array
  391. $queries = array(
  392. 'multi' => array(
  393. 'challenges' => array(),
  394. 'info' => array(),
  395. ),
  396. 'linear' => array(),
  397. );
  398. // Loop thru all of the servers added and categorize them
  399. foreach($this->servers AS $server_id => $instance)
  400. {
  401. // Check to see what kind of server this is and how we can send packets
  402. if($instance->packet_mode() == GameQ_Protocols::PACKET_MODE_LINEAR)
  403. {
  404. $queries['linear'][$server_id] = $instance;
  405. }
  406. else // We can send this out in a multi request
  407. {
  408. // Check to see if we should issue a challenge first
  409. if($instance->hasChallenge())
  410. {
  411. $queries['multi']['challenges'][$server_id] = $instance;
  412. }
  413. // Add this instance to do info query
  414. $queries['multi']['info'][$server_id] = $instance;
  415. }
  416. }
  417. // First lets do the faster, multi queries
  418. if(count($queries['multi']['info']) > 0)
  419. {
  420. $this->requestMulti($queries['multi']);
  421. }
  422. // Now lets do the slower linear queries.
  423. if(count($queries['linear']) > 0)
  424. {
  425. $this->requestLinear($queries['linear']);
  426. }
  427. // Now let's loop the servers and process the response data
  428. foreach($this->servers AS $server_id => $instance)
  429. {
  430. // Lets process this and filter
  431. $data[$server_id] = $this->filterResponse($instance);
  432. }
  433. // Send back the data array, could be empty if nothing went to plan
  434. return $data;
  435. }
  436. }
  437. /* Working Methods */
  438. /**
  439. * Apply all set filters to the data returned by gameservers.
  440. *
  441. * @param GameQ_Protocols $protocol_instance
  442. * @return array
  443. */
  444. protected function filterResponse(GameQ_Protocols $protocol_instance)
  445. {
  446. // Let's pull out the "raw" data we are going to filter
  447. $data = $protocol_instance->processResponse();
  448. // Loop each of the filters we have attached
  449. foreach($this->options['filters'] AS $filter_name => $filter_instance)
  450. {
  451. // Overwrite the data with the "filtered" data
  452. $data = $filter_instance->filter($data, $protocol_instance);
  453. }
  454. return $data;
  455. }
  456. /**
  457. * Process "linear" servers. Servers that do not support multiple packet calls at once. So Slow!
  458. * This method also blocks the socket, you have been warned!!
  459. *
  460. * @param array $servers
  461. * @return boolean
  462. */
  463. protected function requestLinear($servers=array())
  464. {
  465. // Loop thru all the linear servers
  466. foreach($servers AS $server_id => $instance)
  467. {
  468. // First we need to get a socket and we need to block because this is linear
  469. if(($socket = $this->socket_open($instance, TRUE)) === FALSE)
  470. {
  471. // Skip it
  472. continue;
  473. }
  474. // Socket id
  475. $socket_id = (int) $socket;
  476. // See if we have challenges to send off
  477. if($instance->hasChallenge())
  478. {
  479. // Now send off the challenge packet
  480. fwrite($socket, $instance->getPacket('challenge'));
  481. // Read in the challenge response
  482. $instance->challengeResponse(array(fread($socket, 4096)));
  483. // Now we need to parse and apply the challenge response to all the packets that require it
  484. $instance->challengeVerifyAndParse();
  485. }
  486. // Invoke the beforeSend method
  487. $instance->beforeSend();
  488. // Grab the packets we need to send, minus the challenge packet
  489. $packets = $instance->getPacket('!challenge');
  490. // Now loop the packets, begin the slowness
  491. foreach($packets AS $packet_type => $packet)
  492. {
  493. // Add the socket information so we can retreive it easily
  494. $this->sockets = array(
  495. $socket_id => array(
  496. 'server_id' => $server_id,
  497. 'packet_type' => $packet_type,
  498. 'socket' => $socket,
  499. )
  500. );
  501. // Write the packet
  502. fwrite($socket, $packet);
  503. // Get the responses from the query
  504. $responses = $this->sockets_listen();
  505. // Lets look at our responses
  506. foreach($responses AS $socket_id => $response)
  507. {
  508. // Save the response from this packet
  509. $instance->packetResponse($packet_type, $response);
  510. }
  511. }
  512. }
  513. // Now close all the socket(s) and clean up any data
  514. $this->sockets_close();
  515. return TRUE;
  516. }
  517. /**
  518. * Process the servers that support multi requests. That means multiple packets can be sent out at once.
  519. *
  520. * @param array $servers
  521. * @return boolean
  522. */
  523. protected function requestMulti($servers=array())
  524. {
  525. // See if we have any challenges to send off
  526. if(count($servers['challenges']) > 0)
  527. {
  528. // Now lets send off all the challenges
  529. $this->sendChallenge($servers['challenges']);
  530. // Now let's process the challenges
  531. // Loop thru all the instances
  532. foreach($servers['challenges'] AS $server_id => $instance)
  533. {
  534. $instance->challengeVerifyAndParse();
  535. }
  536. }
  537. // Send out all the query packets to get data for
  538. $this->queryServerInfo($servers['info']);
  539. return TRUE;
  540. }
  541. /**
  542. * Send off needed challenges and get the response
  543. *
  544. * @param array $instances
  545. * @return boolean
  546. */
  547. protected function sendChallenge(Array $instances=NULL)
  548. {
  549. // Loop thru all the instances we need to send out challenges for
  550. foreach($instances AS $server_id => $instance)
  551. {
  552. // Make a new socket
  553. if(($socket = $this->socket_open($instance)) === FALSE)
  554. {
  555. // Skip it
  556. continue;
  557. }
  558. // Now write the challenge packet to the socket.
  559. fwrite($socket, $instance->getPacket(GameQ_Protocols::PACKET_CHALLENGE));
  560. // Add the socket information so we can retreive it easily
  561. $this->sockets[(int) $socket] = array(
  562. 'server_id' => $server_id,
  563. 'packet_type' => GameQ_Protocols::PACKET_CHALLENGE,
  564. 'socket' => $socket,
  565. );
  566. // Let's sleep shortly so we are not hammering out calls rapid fire style hogging cpu
  567. usleep($this->write_wait);
  568. }
  569. // Now we need to listen for challenge response(s)
  570. $responses = $this->sockets_listen();
  571. // Lets look at our responses
  572. foreach($responses AS $socket_id => $response)
  573. {
  574. // Back out the server_id we need to update the challenge response for
  575. $server_id = $this->sockets[$socket_id]['server_id'];
  576. // Now set the proper response for the challenge because we will need it later
  577. $this->servers[$server_id]->challengeResponse($response);
  578. }
  579. // Now close all the socket(s) and clean up any data
  580. $this->sockets_close();
  581. return TRUE;
  582. }
  583. /**
  584. * Query the server for actual server information (i.e. info, players, rules, etc...)
  585. *
  586. * @param array $instances
  587. * @return boolean
  588. */
  589. protected function queryServerInfo(Array $instances=NULL)
  590. {
  591. // Loop all the server instances
  592. foreach($instances AS $server_id => $instance)
  593. {
  594. // Invoke the beforeSend method
  595. $instance->beforeSend();
  596. // Get all the non-challenge packets we need to send
  597. $packets = $instance->getPacket('!challenge');
  598. if(count($packets) == 0)
  599. {
  600. // Skip nothing else to do for some reason.
  601. continue;
  602. }
  603. // Now lets send off the packets
  604. foreach($packets AS $packet_type => $packet)
  605. {
  606. // Make a new socket
  607. if(($socket = $this->socket_open($instance)) === FALSE)
  608. {
  609. // Skip it
  610. continue;
  611. }
  612. // Now write the packet to the socket.
  613. fwrite($socket, $packet);
  614. // Add the socket information so we can retreive it easily
  615. $this->sockets[(int) $socket] = array(
  616. 'server_id' => $server_id,
  617. 'packet_type' => $packet_type,
  618. 'socket' => $socket,
  619. );
  620. // Let's sleep shortly so we are not hammering out calls raipd fire style
  621. usleep($this->write_wait);
  622. }
  623. }
  624. // Now we need to listen for packet response(s)
  625. $responses = $this->sockets_listen();
  626. // Lets look at our responses
  627. foreach($responses AS $socket_id => $response)
  628. {
  629. // Back out the server_id
  630. $server_id = $this->sockets[$socket_id]['server_id'];
  631. // Back out the packet type
  632. $packet_type = $this->sockets[$socket_id]['packet_type'];
  633. // Save the response from this packet
  634. $this->servers[$server_id]->packetResponse($packet_type, $response);
  635. }
  636. // Now close all the socket(s) and clean up any data
  637. $this->sockets_close();
  638. return TRUE;
  639. }
  640. /* Sockets/streams stuff */
  641. /**
  642. * Open a new socket based on the instance information
  643. *
  644. * @param GameQ_Protocols $instance
  645. * @param bool $blocking
  646. * @throws GameQException
  647. * @return boolean|resource
  648. */
  649. protected function socket_open(GameQ_Protocols $instance, $blocking=FALSE)
  650. {
  651. // Create the remote address
  652. $remote_addr = sprintf("%s://%s:%d", $instance->transport(), $instance->ip(), $instance->port());
  653. // Create context
  654. $context = stream_context_create(array(
  655. 'socket' => array(
  656. 'bindto' => '0:0', // Bind to any available IP and OS decided port
  657. ),
  658. ));
  659. // Create the socket
  660. if(($socket = @stream_socket_client($remote_addr, $errno = NULL, $errstr = NULL, $this->timeout, STREAM_CLIENT_CONNECT, $context)) !== FALSE)
  661. {
  662. // Set the read timeout on the streams
  663. stream_set_timeout($socket, $this->timeout);
  664. // Set blocking mode
  665. stream_set_blocking($socket, $blocking);
  666. }
  667. else // Throw an error
  668. {
  669. // Check to see if we are in debug mode, if so throw the exception
  670. if($this->debug)
  671. {
  672. throw new GameQException(__METHOD__." Error creating socket to server {$remote_addr}. Error: ".$errstr, $errno);
  673. }
  674. // We didnt create so we need to return false.
  675. return FALSE;
  676. }
  677. unset($context, $remote_addr);
  678. // return the socket
  679. return $socket;
  680. }
  681. /**
  682. * Listen to all the created sockets and return the responses
  683. *
  684. * @return array
  685. */
  686. protected function sockets_listen()
  687. {
  688. // Set the loop to active
  689. $loop_active = TRUE;
  690. // To store the responses
  691. $responses = array();
  692. // To store the sockets
  693. $sockets = array();
  694. // Loop and pull out all the actual sockets we need to listen on
  695. foreach($this->sockets AS $socket_id => $socket_data)
  696. {
  697. // Append the actual socket we are listening to
  698. $sockets[$socket_id] = $socket_data['socket'];
  699. }
  700. // Init some variables
  701. $read = $sockets;
  702. $write = NULL;
  703. $except = NULL;
  704. // Check to see if $read is empty, if so stream_select() will throw a warning
  705. if(empty($read))
  706. {
  707. return $responses;
  708. }
  709. // This is when it should stop
  710. $time_stop = microtime(TRUE) + $this->timeout;
  711. // Let's loop until we break something.
  712. while ($loop_active && microtime(TRUE) < $time_stop)
  713. {
  714. // Now lets listen for some streams, but do not cross the streams!
  715. $streams = stream_select($read, $write, $except, 0, $this->stream_timeout);
  716. // We had error or no streams left, kill the loop
  717. if($streams === FALSE || ($streams <= 0))
  718. {
  719. $loop_active = FALSE;
  720. break;
  721. }
  722. // Loop the sockets that received data back
  723. foreach($read AS $socket)
  724. {
  725. // See if we have a response
  726. if(($response = stream_socket_recvfrom($socket, 8192)) === FALSE)
  727. {
  728. continue; // No response yet so lets continue.
  729. }
  730. // Check to see if the response is empty, if so we are done
  731. // @todo: Verify that this does not affect other protocols, added for Minequery
  732. // Initial testing showed this change did not affect any of the other protocols
  733. if(strlen($response) == 0)
  734. {
  735. // End the while loop
  736. $loop_active = FALSE;
  737. break;
  738. }
  739. // Add the response we got back
  740. $responses[(int) $socket][] = $response;
  741. }
  742. // Because stream_select modifies read we need to reset it each
  743. // time to the original array of sockets
  744. $read = $sockets;
  745. }
  746. // Free up some memory
  747. unset($streams, $read, $write, $except, $sockets, $time_stop, $response);
  748. return $responses;
  749. }
  750. /**
  751. * Close all the open sockets
  752. */
  753. protected function sockets_close()
  754. {
  755. // Loop all the existing sockets, valid or not
  756. foreach($this->sockets AS $socket_id => $data)
  757. {
  758. fclose($data['socket']);
  759. unset($this->sockets[$socket_id]);
  760. }
  761. return TRUE;
  762. }
  763. }
  764. /**
  765. * GameQ Exception Class
  766. *
  767. * Thrown when there is any kind of internal configuration error or
  768. * some unhandled or unexpected error or response.
  769. *
  770. * @author Austin Bischoff <[email protected]>
  771. */
  772. class GameQException extends Exception {}