From c01864ce801919ccc69129a9afda4f6805aa11ce Mon Sep 17 00:00:00 2001 From: =?utf8?q?Roland=20H=C3=A4der?= Date: Wed, 8 Jul 2009 19:46:41 +0000 Subject: [PATCH] Better TCP listener implemented, client pool added, some code moves --- .gitattributes | 4 + application/hub/config.php | 3 + .../interfaces/pool/class_PoolableClient.php | 36 +++++ application/hub/main/class_BaseHubSystem.php | 24 ++++ .../hub/main/listener/class_BaseListener.php | 30 +++- .../listener/class_BaseListenerDecorator.php | 26 +--- application/hub/main/listener/tcp/class_ | 134 ++++++++++++++++++ .../main/listener/tcp/class_TcpListener.php | 66 +++++++-- .../main/listener/udp/class_UdpListener.php | 5 + application/hub/main/pools/class_ | 18 +-- application/hub/main/pools/class_BasePool.php | 17 ++- application/hub/main/pools/client/.htaccess | 1 + .../pools/client/class_DefaultClientPool.php | 81 +++++++++++ 13 files changed, 397 insertions(+), 48 deletions(-) create mode 100644 application/hub/interfaces/pool/class_PoolableClient.php create mode 100644 application/hub/main/listener/tcp/class_ create mode 100644 application/hub/main/pools/client/.htaccess create mode 100644 application/hub/main/pools/client/class_DefaultClientPool.php diff --git a/.gitattributes b/.gitattributes index a9acfd140..c19a615d1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -18,6 +18,7 @@ application/hub/interfaces/nodes/.htaccess -text application/hub/interfaces/nodes/class_NodeHelper.php -text application/hub/interfaces/pool/.htaccess -text application/hub/interfaces/pool/class_Poolable.php -text +application/hub/interfaces/pool/class_PoolableClient.php -text application/hub/interfaces/query/.htaccess -text application/hub/interfaces/query/class_Queryable.php -text application/hub/interfaces/queues/.htaccess -text @@ -46,6 +47,7 @@ application/hub/main/listener/class_BaseListener.php -text application/hub/main/listener/class_BaseListenerDecorator.php -text application/hub/main/listener/decorators/.htaccess -text application/hub/main/listener/tcp/.htaccess -text +application/hub/main/listener/tcp/class_ -text application/hub/main/listener/tcp/class_TcpListener.php -text application/hub/main/listener/tcp/decorators/.htaccess -text application/hub/main/listener/tcp/decorators/class_ClientTcpListenerDecorator.php -text @@ -69,6 +71,8 @@ application/hub/main/nodes/regular/class_HubRegularNode.php -text application/hub/main/pools/.htaccess -text application/hub/main/pools/class_ -text application/hub/main/pools/class_BasePool.php -text +application/hub/main/pools/client/.htaccess -text +application/hub/main/pools/client/class_DefaultClientPool.php -text application/hub/main/pools/listener/.htaccess -text application/hub/main/pools/listener/class_DefaultListenerPool.php -text application/hub/starter.php -text diff --git a/application/hub/config.php b/application/hub/config.php index 1b1f04a28..8065d1ec7 100644 --- a/application/hub/config.php +++ b/application/hub/config.php @@ -78,5 +78,8 @@ $cfg->setConfigEntry('client_tcp_listener_class', "ClientTcpListenerDecorator"); // CFG: CLIENT-UDP-LISTENER-CLASS $cfg->setConfigEntry('client_udp_listener_class', "ClientUdpListenerDecorator"); +// CFG: CLIENT-POOL-CLASS +$cfg->setConfigEntry('client_pool_class', "DefaultClientPool"); + // [EOF] ?> diff --git a/application/hub/interfaces/pool/class_PoolableClient.php b/application/hub/interfaces/pool/class_PoolableClient.php new file mode 100644 index 000000000..36f258d76 --- /dev/null +++ b/application/hub/interfaces/pool/class_PoolableClient.php @@ -0,0 +1,36 @@ + + * @version 0.0.0 + * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 Hub Developer Team + * @license GNU GPL 3.0 or any newer version + * @link http://www.ship-simu.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +interface PoolableClient extends FrameworkInterface { + /** + * Adds a socket resource to the client pool + * + * @param $socketResource A valid (must be!) socket resource + * @return void + * @todo 0% done + */ + function addClient ($socketResource); +} + +// +?> diff --git a/application/hub/main/class_BaseHubSystem.php b/application/hub/main/class_BaseHubSystem.php index 90e67e1d7..32497dbb4 100644 --- a/application/hub/main/class_BaseHubSystem.php +++ b/application/hub/main/class_BaseHubSystem.php @@ -27,6 +27,30 @@ class BaseHubSystem extends BaseFrameworkSystem { */ private $nodeInstance = null; + /** + * Listener instance + */ + private $listenerInstance = null; + + /** + * Setter for listener instance + * + * @param $listenerInstance A Listenable instance + * @return void + */ + protected final function setListenerInstance (Listenable $listenerInstance) { + $this->listenerInstance = $listenerInstance; + } + + /** + * Getter for listener instance + * + * @return $listenerInstance A Listenable instance + */ + protected final function getListenerInstance () { + return $this->listenerInstance; + } + /** * Protected constructor * diff --git a/application/hub/main/listener/class_BaseListener.php b/application/hub/main/listener/class_BaseListener.php index 51088aa05..31809ff5a 100644 --- a/application/hub/main/listener/class_BaseListener.php +++ b/application/hub/main/listener/class_BaseListener.php @@ -48,7 +48,12 @@ class BaseListener extends BaseHubSystem { /** * Socket resource */ - private $socketResource = null; + private $socketResource = false; + + /** + * A client pool instance + */ + private $poolInstance = null; /** * Protected constructor @@ -164,7 +169,7 @@ class BaseListener extends BaseHubSystem { * @return void */ protected final function setSocketResource ($socketResource) { - $this->setSocketResource = $setSocketResource; + $this->socketResource = $setSocketResource; } /** @@ -173,7 +178,26 @@ class BaseListener extends BaseHubSystem { * @return $socketResource The socket resource we shall set */ protected final function getSocketResource () { - return $this->setSocketResource; + return $this->socketResource; + } + + /** + * Setter for client pool instance + * + * @param $poolInstance The client pool instance we shall set + * @return void + */ + protected final function setPoolInstance (PoolableClient $poolInstance) { + $this->poolInstance = $setPoolInstance; + } + + /** + * Getter for client pool instance + * + * @return $poolInstance The client pool instance we shall set + */ + protected final function getPoolInstance () { + return $this->poolInstance; } } diff --git a/application/hub/main/listener/class_BaseListenerDecorator.php b/application/hub/main/listener/class_BaseListenerDecorator.php index c228078df..912f313b3 100644 --- a/application/hub/main/listener/class_BaseListenerDecorator.php +++ b/application/hub/main/listener/class_BaseListenerDecorator.php @@ -21,12 +21,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -class BaseListenerDecorator extends BaseFrameworkSystem { - /** - * Decorated listener instance - */ - private $listenerInstance = null; - +class BaseListenerDecorator extends BaseHubSystem { /** * Protected constructor * @@ -42,25 +37,6 @@ class BaseListenerDecorator extends BaseFrameworkSystem { $this->removeSystemArray(); } - /** - * Setter for listener instance - * - * @param $listenerInstance A Listenable instance - * @return void - */ - protected final function setListenerInstance (Listenable $listenerInstance) { - $this->listenerInstance = $listenerInstance; - } - - /** - * Getter for listener instance - * - * @return $listenerInstance A Listenable instance - */ - protected final function getListenerInstance () { - return $this->listenerInstance; - } - /** * Getter for listen address * diff --git a/application/hub/main/listener/tcp/class_ b/application/hub/main/listener/tcp/class_ new file mode 100644 index 000000000..67084818a --- /dev/null +++ b/application/hub/main/listener/tcp/class_ @@ -0,0 +1,134 @@ +while (count($clients) > 0) { + // create a copy, so $clients doesn't get modified by socket_select() + $read = $clients; + + // get a list of all the clients that have data to be read from + // if there are no clients with data, go to next iteration + $left = @socket_select($read, $write = null, $except = null, 0, 150); + if ($left < 1) { + continue; + } + + // check if there is a client trying to connect + if (in_array($mainSocket, $read)) { + // accept the client, and add him to the $clients array + $new_sock = socket_accept($mainSocket); + $clients[] = $new_sock; + + // send the client a welcome message + socket_write($new_sock, "No noobs, but I'll make an exception. :)\n". + "There are ".(count($clients) - 1)." client(s) connected to the server.\n"); + + socket_getpeername($new_sock, $ip); + out(__FILE__, __LINE__, '['.date('m/d/Y:H:i:s', time())."]:New client connected: {$ip}"); + + // Notify all chatter + if (count($clients) > 2) { + foreach ($clients as $send_sock) { + if ($send_sock != $mainSocket && $send_sock != $new_sock) { + socket_write($send_sock, "Server: Chatter has joined from {$ip}. There are now ".(count($clients) - 1)." clients.\n"); + } + } + } + + // remove the listening socket from the clients-with-data array + $key = array_search($mainSocket, $read); + unset($read[$key]); + } + + // loop through all the clients that have data to read from + foreach ($read as $read_sock) { + // Get client data + socket_getpeername($read_sock, $ip); + + // read until newline or 1024 bytes + // socket_read while show errors when the client is disconnected, so silence the error messages + $data = @socket_read($read_sock, 1024, PHP_NORMAL_READ); + + // check if the client is disconnected + if (($data === false) || (in_array(strtolower(trim($data)), $leaving))) { + + // remove client for $clients array + $key = array_search($read_sock, $clients); + unset($clients[$key]); + out(__FILE__, __LINE__, '['.date('m/d/Y:H:i:s', time())."]:Client from {$ip} disconnected. Left: ".(count($clients) - 1).""); + + // Notify all chatter + if (count($clients) > 1) { + foreach ($clients as $send_sock) { + if ($send_sock != $mainSocket) { + socket_write($send_sock, "Server: Chatter from {$ip} has logged out. ".(count($clients) - 1)." client(s) left.\n"); + } + } + } + + // continue to the next client to read from, if any + socket_write($read_sock, "Server: Good bye.\n"); + socket_shutdown($read_sock, 2); + socket_close($read_sock); + continue; + } elseif (in_array(trim($data), $shutdown)) { + // Is he allowed to shutdown? + if (!in_array($ip, $masters)) { + out(__FILE__, __LINE__, '['.date('m/d/Y:H:i:s', time())."]:Client {$ip} has tried to shutdown the server!"); + socket_write($read_sock, "Server: You are not allowed to shutdown the server!\n"); + $data = ""; + continue; + } + + // Close all connections a leave here + foreach ($clients as $client) { + // Send message to client + if ($client !== $mainSocket && $client != $read_sock) { + socket_write($client, "Server: Shutting down! Thank you for joining us.\n"); + } + + // Quit him + socket_shutdown($client, 2); + socket_close($client); + } // end foreach + + // Leave the loop + $data = ""; + $clients = array(); + continue; + } + + // trim off the trailing/beginning white spaces + $data = trim($data); + + // Test for HTML codes + $tags = strip_tags($data); + + // check if there is any data after trimming off the spaces + if (!empty($data) && $tags == $data && count($clients) > 2) { + // Send confirmation to "chatter" + socket_write($read_sock, "\nServer: Message accepted.\n"); + + // send this to all the clients in the $clients array (except the first one, which is a listening socket) + foreach ($clients as $send_sock) { + + // if its the listening sock or the client that we got the message from, go to the next one in the list + if ($send_sock == $mainSocket || $send_sock == $read_sock) + continue; + + // write the message to the client -- add a newline character to the end of the message + socket_write($send_sock, "{$ip}:{$data}\n"); + + } // end of broadcast foreach + } elseif ($tags != $data) { + // HTML codes are not allowed + out(__FILE__, __LINE__, '['.date('m/d/Y:H:i:s', time())."]:Client {$ip} has entered HTML code!"); + socket_write($read_sock, "Server: HTML is forbidden!\n"); + } elseif ((count($clients) == 2) && ($read_sock != $mainSocket)) { + // No one else will hear the "chatter" + out(__FILE__, __LINE__, '['.date('m/d/Y:H:i:s', time())."]:Client {$ip} speaks with himself."); + socket_write($read_sock, "Server: No one will hear you!\n"); + } + } // end of reading foreach +} + +// close the listening socket +socket_close($mainSocket); + +?> diff --git a/application/hub/main/listener/tcp/class_TcpListener.php b/application/hub/main/listener/tcp/class_TcpListener.php index 61b3dca83..9e6f3f866 100644 --- a/application/hub/main/listener/tcp/class_TcpListener.php +++ b/application/hub/main/listener/tcp/class_TcpListener.php @@ -59,17 +59,67 @@ class TcpListener extends BaseListener implements Listenable { * @throws InvalidSocketException Thrown if the socket could not be initialized */ public function initListener() { - // Try to open a TCP socket - $socket = stream_socket_server('tcp://' . $this->getListenAddress() . ':' . $this->getListenPort(), $errno, $errstr); + // Create a streaming socket, of type TCP/IP + $mainSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); - // Is the socket a valid resource or do we have any error? - if ((!is_resource($socket)) || ($errno > 0)) { - // Then throw an InvalidSocketException - throw new InvalidSocketException(array($this, gettype($socket), $errno, $errstr), BaseListener::EXCEPTION_INVALID_SOCKET); + // Is the socket resource valid? + if (!is_resource($mainSocket)) { + // Something bad happened + throw new InvalidSocketException(array($this, gettype($mainSocket), 0, 'invalid'), BaseListener::EXCEPTION_INVALID_SOCKET); } // END - if - // Remember the socket in our class - $this->setSocketResource($socket); + // Get socket error code for verification + $socketError = socket_last_error($mainSocket); + + // Check if there was an error else + if ($socketError > 0) { + // Then throw again + throw new InvalidSocketException(array($this, gettype($mainSocket), $socketError, socket_strerror($socketError)), BaseListener::EXCEPTION_INVALID_SOCKET); + } // END - if + + // Set the option to reuse the port + if (!socket_set_option($mainSocket, SOL_SOCKET, SO_REUSEADDR, 1)) { + // Get socket error code for verification + $socketError = socket_last_error($mainSocket); + + // And throw again + throw new InvalidSocketException(array($this, gettype($mainSocket), $socketError, socket_strerror($socketError)), BaseListener::EXCEPTION_INVALID_SOCKET); + } // END - if + + // "Bind" the socket to the given address, on given port so this means + // that all connections on this port are now our resposibility to + // send/recv data, disconnect, etc.. + if (!socket_bind($mainSocket, $this->getListenAddress(), $this->getListenPort())) { + // Get socket error code for verification + $socketError = socket_last_error($mainSocket); + + // And throw again + throw new InvalidSocketException(array($this, gettype($mainSocket), $socketError, socket_strerror($socketError)), BaseListener::EXCEPTION_INVALID_SOCKET); + } // END - if + + // Start listen for connections + if (!socket_listen($mainSocket)) { + // Get socket error code for verification + $socketError = socket_last_error($mainSocket); + + // And throw again + throw new InvalidSocketException(array($this, gettype($mainSocket), $socketError, socket_strerror($socketError)), BaseListener::EXCEPTION_INVALID_SOCKET); + } // END - if + + // Set the main socket + $this->setSocketResource($mainSocket); + + // Initialize the client pool instance + $poolInstance = ObjectFactory::createObjectByConfiguredName('client_pool_class', array($this)); + + // Add main socket + $poolInstance->addClient($mainSocket); + + // And add it to this listener + $this->setPoolInstance($poolInstance); + + // Output message + $this->getDebugInstance()->output('LISTENER: TCP listener now ready on IP ' . $this->getListenAddress() . ', port ' . $this->getListenPort() . ' for service.'); } } diff --git a/application/hub/main/listener/udp/class_UdpListener.php b/application/hub/main/listener/udp/class_UdpListener.php index b3583e0c7..9edd449e1 100644 --- a/application/hub/main/listener/udp/class_UdpListener.php +++ b/application/hub/main/listener/udp/class_UdpListener.php @@ -58,6 +58,8 @@ class UdpListener extends BaseListener implements Listenable { * @return void * @throws InvalidSocketException Thrown if the socket is invalid or an * error was detected. + * @todo stream_socket_server() was declared slow by some user comments. + * @todo Please rewrite it to socket_create() and its brothers. */ public function initListener() { // Try to open a UDP socket @@ -71,6 +73,9 @@ class UdpListener extends BaseListener implements Listenable { // Remember the socket in our class $this->setSocketResource($socket); + + // Output message + $this->getDebugInstance()->output('LISTENER: UDP listener now ready on IP ' . $this->getListenAddress() . ', port ' . $this->getListenPort() . ' for service.'); } } diff --git a/application/hub/main/pools/class_ b/application/hub/main/pools/class_ index 83265f335..716176243 100644 --- a/application/hub/main/pools/class_ +++ b/application/hub/main/pools/class_ @@ -35,29 +35,29 @@ class ???Pool extends BasePool implements Poolable { /** * Creates an instance of this class * - * @param $nodeInstance A NodeHelper instance - * @return $listenerInstance An instance a prepared listener class + * @param $listenerInstance A Listenable instance + * @return $poolInstance An instance a Poolable class */ - public final static function create???Pool (NodeHelper $nodeInstance) { + public final static function create???Pool (Listenable $listenerInstance) { // Get new instance - $listenerInstance = new ???Pool(); + $poolInstance = new ???Pool(); // Set the application instance - $listenerInstance->setNodeInstance($nodeInstance); + $poolInstance->setListenerInstance($listenerInstance); // Return the prepared instance - return $listenerInstance; + return $poolInstance; } /** * Adds a listener instance to this pool * - * @param $listenerInstance An instance of a Listenable class + * @param $poolInstance An instance of a Listenable class * @return void * @todo 0% done */ - public function addListener (Listenable $listenerInstance) { - $this->partialStub('Need to implement this method. listenerInstance=' . $listenerInstance->__toString()); + public function addListener (Listenable $poolInstance) { + $this->partialStub('Need to implement this method. listenerInstance=' . $poolInstance->__toString()); } } diff --git a/application/hub/main/pools/class_BasePool.php b/application/hub/main/pools/class_BasePool.php index f49ba860f..efa9aa9da 100644 --- a/application/hub/main/pools/class_BasePool.php +++ b/application/hub/main/pools/class_BasePool.php @@ -23,9 +23,9 @@ */ class BasePool extends BaseHubSystem { /** - * A list of instances for this pool + * A list of pool entries */ - private $instancePool = array(); + private $poolEntries = array(); /** * Protected constructor @@ -45,9 +45,20 @@ class BasePool extends BaseHubSystem { * @param $poolSegment Name of the pool segment * @param $instance An instance of a class we should add to the pool * @return void + * @todo Can we use Listenable instead of FrameworkInterface ? */ protected final function addInstance($group, $poolName, FrameworkInterface $instance) { - $this->instancePool[$group][$poolName][] = $instance; + $this->poolEntries[$group][$poolName][] = $instance; + } + + /** + * Adds an entry to the pool + * + * @param $poolEntry The new pool entry we should add + * @return void + */ + protected final function addPoolEntry ($poolEntry) { + $this->poolEntries[] = $poolEntry; } } diff --git a/application/hub/main/pools/client/.htaccess b/application/hub/main/pools/client/.htaccess new file mode 100644 index 000000000..3a4288278 --- /dev/null +++ b/application/hub/main/pools/client/.htaccess @@ -0,0 +1 @@ +Deny from all diff --git a/application/hub/main/pools/client/class_DefaultClientPool.php b/application/hub/main/pools/client/class_DefaultClientPool.php new file mode 100644 index 000000000..9a2922e51 --- /dev/null +++ b/application/hub/main/pools/client/class_DefaultClientPool.php @@ -0,0 +1,81 @@ + + * @version 0.0.0 + * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 Hub Developer Team + * @license GNU GPL 3.0 or any newer version + * @link http://www.ship-simu.org + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +class DefaultClientPool extends BasePool implements PoolableClient { + /** + * Protected constructor + * + * @return void + */ + protected function __construct () { + // Call parent constructor + parent::__construct(__CLASS__); + } + + /** + * Creates an instance of this class + * + * @param $listenerInstance A Listenable instance + * @return $poolInstance An instance a Poolable class + */ + public final static function createDefaultClientPool (Listenable $listenerInstance) { + // Get new instance + $poolInstance = new DefaultClientPool(); + + // Set the application instance + $poolInstance->setListenerInstance($listenerInstance); + + // Return the prepared instance + return $poolInstance; + } + + /** + * Adds a socket resource to the client pool + * + * @param $socketResource A valid (must be!) socket resource + * @return void + * @throws InvalidSocketException If the given resource is invalid or errorous + */ + public function addClient ($socketResource) { + // Is it a valid resource? + if (!is_resource($socketResource)) { + // Throw an exception + throw new InvalidSocketException(array($this, gettype($socketResource), 0, 'invalid'), BaseListener::EXCEPTION_INVALID_SOCKET); + } // END - if + + // Get error code + $errorCode = socket_last_error($socketResource); + + // Is it without any errors? + if ($errorCode > 0) { + // Throw an exception again + throw new InvalidSocketException(array($this, gettype($socketResource), $errorCode, socket_strerror($errorCode)), BaseListener::EXCEPTION_INVALID_SOCKET); + } // END - if + + // Add it finally to the pool + $this->addPoolEntry($socketResource); + } +} + +// +?> -- 2.39.2