1
0

Server.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  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;
  19. use GameQ\Exception\Server as Exception;
  20. /**
  21. * Server class to represent each server entity
  22. *
  23. * @author Austin Bischoff <[email protected]>
  24. */
  25. class Server
  26. {
  27. /*
  28. * Server array keys
  29. */
  30. const SERVER_TYPE = 'type';
  31. const SERVER_HOST = 'host';
  32. const SERVER_ID = 'id';
  33. const SERVER_OPTIONS = 'options';
  34. /*
  35. * Server options keys
  36. */
  37. /*
  38. * Use this option when the query_port and client connect ports are different
  39. */
  40. const SERVER_OPTIONS_QUERY_PORT = 'query_port';
  41. /**
  42. * The protocol class for this server
  43. *
  44. * @type \GameQ\Protocol
  45. */
  46. protected $protocol = null;
  47. /**
  48. * Id of this server
  49. *
  50. * @type string
  51. */
  52. public $id = null;
  53. /**
  54. * IP Address of this server
  55. *
  56. * @type string
  57. */
  58. public $ip = null;
  59. /**
  60. * The server's client port (connect port)
  61. *
  62. * @type int
  63. */
  64. public $port_client = null;
  65. /**
  66. * The server's query port
  67. *
  68. * @type int
  69. */
  70. public $port_query = null;
  71. /**
  72. * Holds other server specific options
  73. *
  74. * @type array
  75. */
  76. protected $options = [];
  77. /**
  78. * Holds the sockets already open for this server
  79. *
  80. * @type array
  81. */
  82. protected $sockets = [];
  83. /**
  84. * Construct the class with the passed options
  85. *
  86. * @param array $server_info
  87. *
  88. * @throws \GameQ\Exception\Server
  89. */
  90. public function __construct(array $server_info = [])
  91. {
  92. // Check for server type
  93. if (!array_key_exists(self::SERVER_TYPE, $server_info) || empty($server_info[self::SERVER_TYPE])) {
  94. throw new Exception("Missing server info key '" . self::SERVER_TYPE . "'!");
  95. }
  96. // Check for server host
  97. if (!array_key_exists(self::SERVER_HOST, $server_info) || empty($server_info[self::SERVER_HOST])) {
  98. throw new Exception("Missing server info key '" . self::SERVER_HOST . "'!");
  99. }
  100. // IP address and port check
  101. $this->checkAndSetIpPort($server_info[self::SERVER_HOST]);
  102. // Check for server id
  103. if (array_key_exists(self::SERVER_ID, $server_info) && !empty($server_info[self::SERVER_ID])) {
  104. // Set the server id
  105. $this->id = $server_info[self::SERVER_ID];
  106. } else {
  107. // Make an id so each server has an id when returned
  108. $this->id = sprintf('%s:%d', $this->ip, $this->port_client);
  109. }
  110. // Check and set server options
  111. if (array_key_exists(self::SERVER_OPTIONS, $server_info)) {
  112. // Set the options
  113. $this->options = $server_info[self::SERVER_OPTIONS];
  114. }
  115. try {
  116. // Make the protocol class for this type
  117. $class = new \ReflectionClass(
  118. sprintf('GameQ\\Protocols\\%s', ucfirst(strtolower($server_info[self::SERVER_TYPE])))
  119. );
  120. $this->protocol = $class->newInstanceArgs([$this->options]);
  121. } catch (\ReflectionException $e) {
  122. throw new Exception("Unable to locate Protocols class for '{$server_info[self::SERVER_TYPE]}'!");
  123. }
  124. // Check and set any server options
  125. $this->checkAndSetServerOptions();
  126. unset($server_info, $class);
  127. }
  128. /**
  129. * Check and set the ip address for this server
  130. *
  131. * @param $ip_address
  132. *
  133. * @throws \GameQ\Exception\Server
  134. */
  135. protected function checkAndSetIpPort($ip_address)
  136. {
  137. // Test for IPv6
  138. if (substr_count($ip_address, ':') > 1) {
  139. // See if we have a port, input should be in the format [::1]:27015 or similar
  140. if (strstr($ip_address, ']:')) {
  141. // Explode to get port
  142. $server_addr = explode(':', $ip_address);
  143. // Port is the last item in the array, remove it and save
  144. $this->port_client = (int)array_pop($server_addr);
  145. // The rest is the address, recombine
  146. $this->ip = implode(':', $server_addr);
  147. unset($server_addr);
  148. } else {
  149. // Just the IPv6 address, no port defined, fail
  150. throw new Exception(
  151. "The host address '{$ip_address}' is missing the port. All "
  152. . "servers must have a port defined!"
  153. );
  154. }
  155. // Now let's validate the IPv6 value sent, remove the square brackets ([]) first
  156. if (!filter_var(trim($this->ip, '[]'), FILTER_VALIDATE_IP, ['flags' => FILTER_FLAG_IPV6,])) {
  157. throw new Exception("The IPv6 address '{$this->ip}' is invalid.");
  158. }
  159. } else {
  160. // We have IPv4 with a port defined
  161. if (strstr($ip_address, ':')) {
  162. list($this->ip, $this->port_client) = explode(':', $ip_address);
  163. // Type case the port
  164. $this->port_client = (int)$this->port_client;
  165. } else {
  166. // No port, fail
  167. throw new Exception(
  168. "The host address '{$ip_address}' is missing the port. All "
  169. . "servers must have a port defined!"
  170. );
  171. }
  172. // Validate the IPv4 value, if FALSE is not a valid IP, maybe a hostname. Try to resolve
  173. if (!filter_var($this->ip, FILTER_VALIDATE_IP, ['flags' => FILTER_FLAG_IPV4,])
  174. && $this->ip === gethostbyname($this->ip)
  175. ) {
  176. // When gethostbyname() fails it returns the original string
  177. // so if ip and the result from gethostbyname() are equal this failed.
  178. throw new Exception("Unable to resolve the host '{$this->ip}' to an IP address.");
  179. }
  180. }
  181. }
  182. /**
  183. * Check and set any server specific options
  184. */
  185. protected function checkAndSetServerOptions()
  186. {
  187. // Specific query port defined
  188. if (array_key_exists(self::SERVER_OPTIONS_QUERY_PORT, $this->options)) {
  189. $this->port_query = (int)$this->options[self::SERVER_OPTIONS_QUERY_PORT];
  190. } else {
  191. // Do math based on the protocol class
  192. $this->port_query = $this->protocol->findQueryPort($this->port_client);
  193. }
  194. }
  195. /**
  196. * Set an option for this server
  197. *
  198. * @param $key
  199. * @param $value
  200. *
  201. * @return $this
  202. */
  203. public function setOption($key, $value)
  204. {
  205. $this->options[$key] = $value;
  206. return $this; // Make chainable
  207. }
  208. /**
  209. * Return set option value
  210. *
  211. * @param mixed $key
  212. *
  213. * @return mixed
  214. */
  215. public function getOption($key)
  216. {
  217. return (array_key_exists($key, $this->options)) ? $this->options[$key] : null;
  218. }
  219. /**
  220. * Get the ID for this server
  221. *
  222. * @return string
  223. */
  224. public function id()
  225. {
  226. return $this->id;
  227. }
  228. /**
  229. * Get the IP address for this server
  230. *
  231. * @return string
  232. */
  233. public function ip()
  234. {
  235. return $this->ip;
  236. }
  237. /**
  238. * Get the client port for this server
  239. *
  240. * @return int
  241. */
  242. public function portClient()
  243. {
  244. return $this->port_client;
  245. }
  246. /**
  247. * Get the query port for this server
  248. *
  249. * @return int
  250. */
  251. public function portQuery()
  252. {
  253. return $this->port_query;
  254. }
  255. /**
  256. * Return the protocol class for this server
  257. *
  258. * @return \GameQ\Protocol
  259. */
  260. public function protocol()
  261. {
  262. return $this->protocol;
  263. }
  264. /**
  265. * Get the join link for this server
  266. *
  267. * @return string
  268. */
  269. public function getJoinLink()
  270. {
  271. return sprintf($this->protocol->joinLink(), $this->ip, $this->portClient());
  272. }
  273. /*
  274. * Socket holding
  275. */
  276. /**
  277. * Add a socket for this server to be reused
  278. *
  279. * @codeCoverageIgnore
  280. *
  281. * @param \GameQ\Query\Core $socket
  282. */
  283. public function socketAdd(Query\Core $socket)
  284. {
  285. $this->sockets[] = $socket;
  286. }
  287. /**
  288. * Get a socket from the list to reuse, if any are available
  289. *
  290. * @codeCoverageIgnore
  291. *
  292. * @return \GameQ\Query\Core|null
  293. */
  294. public function socketGet()
  295. {
  296. $socket = null;
  297. if (count($this->sockets) > 0) {
  298. $socket = array_pop($this->sockets);
  299. }
  300. return $socket;
  301. }
  302. /**
  303. * Clear any sockets still listed and attempt to close them
  304. *
  305. * @codeCoverageIgnore
  306. */
  307. public function socketCleanse()
  308. {
  309. // Close all of the sockets available
  310. foreach ($this->sockets as $socket) {
  311. /* @var $socket \GameQ\Query\Core */
  312. $socket->close();
  313. }
  314. // Reset the sockets list
  315. $this->sockets = [];
  316. }
  317. }