Native.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  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. namespace GameQ\Query;
  19. use GameQ\Exception\Query as Exception;
  20. /**
  21. * Native way of querying servers
  22. *
  23. * @author Austin Bischoff <[email protected]>
  24. */
  25. class Native extends Core
  26. {
  27. /**
  28. * Get the current socket or create one and return
  29. *
  30. * @return resource|null
  31. * @throws \GameQ\Exception\Query
  32. */
  33. public function get()
  34. {
  35. // No socket for this server, make one
  36. if (is_null($this->socket)) {
  37. $this->create();
  38. }
  39. return $this->socket;
  40. }
  41. /**
  42. * Write data to the socket
  43. *
  44. * @param string $data
  45. *
  46. * @return int The number of bytes written
  47. * @throws \GameQ\Exception\Query
  48. */
  49. public function write($data)
  50. {
  51. try {
  52. // No socket for this server, make one
  53. if (is_null($this->socket)) {
  54. $this->create();
  55. }
  56. // Send the packet
  57. return fwrite($this->socket, $data);
  58. } catch (\Exception $e) {
  59. throw new Exception($e->getMessage(), $e->getCode(), $e);
  60. }
  61. }
  62. /**
  63. * Close the current socket
  64. */
  65. public function close()
  66. {
  67. if ($this->socket) {
  68. fclose($this->socket);
  69. $this->socket = null;
  70. }
  71. }
  72. /**
  73. * Create a new socket for this query
  74. *
  75. * @throws \GameQ\Exception\Query
  76. */
  77. protected function create()
  78. {
  79. // Create the remote address
  80. $remote_addr = sprintf("%s://%s:%d", $this->transport, $this->ip, $this->port);
  81. // Create context
  82. $context = stream_context_create([
  83. 'socket' => [
  84. 'bindto' => '0:0', // Bind to any available IP and OS decided port
  85. ],
  86. ]);
  87. // Define these first
  88. $errno = null;
  89. $errstr = null;
  90. // Create the socket
  91. if (($this->socket =
  92. @stream_socket_client($remote_addr, $errno, $errstr, $this->timeout, STREAM_CLIENT_CONNECT, $context))
  93. !== false
  94. ) {
  95. // Set the read timeout on the streams
  96. stream_set_timeout($this->socket, $this->timeout);
  97. // Set blocking mode
  98. stream_set_blocking($this->socket, $this->blocking);
  99. // Set the read buffer
  100. stream_set_read_buffer($this->socket, 0);
  101. // Set the write buffer
  102. stream_set_write_buffer($this->socket, 0);
  103. } else {
  104. // Reset socket
  105. $this->socket = null;
  106. // Something bad happened, throw query exception
  107. throw new Exception(
  108. __METHOD__ . " - Error creating socket to server {$this->ip}:{$this->port}. Error: " . $errstr,
  109. $errno
  110. );
  111. }
  112. }
  113. /**
  114. * Pull the responses out of the stream
  115. *
  116. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  117. * @SuppressWarnings(PHPMD.NPathComplexity)
  118. *
  119. * @param array $sockets
  120. * @param int $timeout
  121. * @param int $stream_timeout
  122. *
  123. * @return array Raw responses
  124. */
  125. public function getResponses(array $sockets, $timeout, $stream_timeout)
  126. {
  127. // Set the loop to active
  128. $loop_active = true;
  129. // Will hold the responses read from the sockets
  130. $responses = [];
  131. // To store the sockets
  132. $sockets_tmp = [];
  133. // Loop and pull out all the actual sockets we need to listen on
  134. foreach ($sockets as $socket_id => $socket_data) {
  135. // Get the socket
  136. /* @var $socket \GameQ\Query\Core */
  137. $socket = $socket_data['socket'];
  138. // Append the actual socket we are listening to
  139. $sockets_tmp[$socket_id] = $socket->get();
  140. unset($socket);
  141. }
  142. // Init some variables
  143. $read = $sockets_tmp;
  144. $write = null;
  145. $except = null;
  146. // Check to see if $read is empty, if so stream_select() will throw a warning
  147. if (empty($read)) {
  148. return $responses;
  149. }
  150. // This is when it should stop
  151. $time_stop = microtime(true) + $timeout;
  152. // Let's loop until we break something.
  153. while ($loop_active && microtime(true) < $time_stop) {
  154. // Check to make sure $read is not empty, if so we are done
  155. if (empty($read)) {
  156. break;
  157. }
  158. // Now lets listen for some streams, but do not cross the streams!
  159. $streams = stream_select($read, $write, $except, 0, $stream_timeout);
  160. // We had error or no streams left, kill the loop
  161. if ($streams === false || ($streams <= 0)) {
  162. break;
  163. }
  164. // Loop the sockets that received data back
  165. foreach ($read as $socket) {
  166. /* @var $socket resource */
  167. // See if we have a response
  168. if (($response = fread($socket, 32768)) === false) {
  169. continue; // No response yet so lets continue.
  170. }
  171. // Check to see if the response is empty, if so we are done with this server
  172. if (strlen($response) == 0) {
  173. // Remove this server from any future read loops
  174. unset($sockets_tmp[(int)$socket]);
  175. continue;
  176. }
  177. // Add the response we got back
  178. $responses[(int)$socket][] = $response;
  179. }
  180. // Because stream_select modifies read we need to reset it each time to the original array of sockets
  181. $read = $sockets_tmp;
  182. }
  183. // Free up some memory
  184. unset($streams, $read, $write, $except, $sockets_tmp, $time_stop, $response);
  185. // Return all of the responses, may be empty if something went wrong
  186. return $responses;
  187. }
  188. }