Server.php 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  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.
  173. if (! filter_var($this->ip, FILTER_VALIDATE_IP, ['flags' => FILTER_FLAG_IPV4,])) {
  174. // Try to resolve the hostname to IPv4
  175. $resolved = gethostbyname($this->ip);
  176. // When gethostbyname() fails it returns the original string
  177. if ($this->ip === $resolved) {
  178. // so if ip and the result from gethostbyname() are equal this failed.
  179. throw new Exception("Unable to resolve the host '{$this->ip}' to an IP address.");
  180. } else {
  181. $this->ip = $resolved;
  182. }
  183. }
  184. }
  185. }
  186. /**
  187. * Check and set any server specific options
  188. */
  189. protected function checkAndSetServerOptions()
  190. {
  191. // Specific query port defined
  192. if (array_key_exists(self::SERVER_OPTIONS_QUERY_PORT, $this->options)) {
  193. $this->port_query = (int)$this->options[self::SERVER_OPTIONS_QUERY_PORT];
  194. } else {
  195. // Do math based on the protocol class
  196. $this->port_query = $this->protocol->findQueryPort($this->port_client);
  197. }
  198. }
  199. /**
  200. * Set an option for this server
  201. *
  202. * @param $key
  203. * @param $value
  204. *
  205. * @return $this
  206. */
  207. public function setOption($key, $value)
  208. {
  209. $this->options[$key] = $value;
  210. return $this; // Make chainable
  211. }
  212. /**
  213. * Return set option value
  214. *
  215. * @param mixed $key
  216. *
  217. * @return mixed
  218. */
  219. public function getOption($key)
  220. {
  221. return (array_key_exists($key, $this->options)) ? $this->options[$key] : null;
  222. }
  223. public function getOptions()
  224. {
  225. return $this->options;
  226. }
  227. /**
  228. * Get the ID for this server
  229. *
  230. * @return string
  231. */
  232. public function id()
  233. {
  234. return $this->id;
  235. }
  236. /**
  237. * Get the IP address for this server
  238. *
  239. * @return string
  240. */
  241. public function ip()
  242. {
  243. return $this->ip;
  244. }
  245. /**
  246. * Get the client port for this server
  247. *
  248. * @return int
  249. */
  250. public function portClient()
  251. {
  252. return $this->port_client;
  253. }
  254. /**
  255. * Get the query port for this server
  256. *
  257. * @return int
  258. */
  259. public function portQuery()
  260. {
  261. return $this->port_query;
  262. }
  263. /**
  264. * Return the protocol class for this server
  265. *
  266. * @return \GameQ\Protocol
  267. */
  268. public function protocol()
  269. {
  270. return $this->protocol;
  271. }
  272. /**
  273. * Get the join link for this server
  274. *
  275. * @return string
  276. */
  277. public function getJoinLink()
  278. {
  279. return sprintf($this->protocol->joinLink(), $this->ip, $this->portClient());
  280. }
  281. /*
  282. * Socket holding
  283. */
  284. /**
  285. * Add a socket for this server to be reused
  286. *
  287. * @codeCoverageIgnore
  288. *
  289. * @param \GameQ\Query\Core $socket
  290. */
  291. public function socketAdd(Query\Core $socket)
  292. {
  293. $this->sockets[] = $socket;
  294. }
  295. /**
  296. * Get a socket from the list to reuse, if any are available
  297. *
  298. * @codeCoverageIgnore
  299. *
  300. * @return \GameQ\Query\Core|null
  301. */
  302. public function socketGet()
  303. {
  304. $socket = null;
  305. if (count($this->sockets) > 0) {
  306. $socket = array_pop($this->sockets);
  307. }
  308. return $socket;
  309. }
  310. /**
  311. * Clear any sockets still listed and attempt to close them
  312. *
  313. * @codeCoverageIgnore
  314. */
  315. public function socketCleanse()
  316. {
  317. // Close all of the sockets available
  318. foreach ($this->sockets as $socket) {
  319. /* @var $socket \GameQ\Query\Core */
  320. $socket->close();
  321. }
  322. // Reset the sockets list
  323. $this->sockets = [];
  324. }
  325. }