X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=application%2Fhub%2Fmain%2Flistener%2Ftcp%2Fclass_TcpListener.php;h=680ab86b5e45b492f15e3bcebb5fa74124255f7b;hb=88fd171613ace75f26534529af8b2af213217d33;hp=9773fc8710644e7947803d88203afcf598a4dd5a;hpb=c2c103ba61ec1462e2ef69a3f144ecfe6aeb4ebd;p=hub.git diff --git a/application/hub/main/listener/tcp/class_TcpListener.php b/application/hub/main/listener/tcp/class_TcpListener.php index 9773fc871..680ab86b5 100644 --- a/application/hub/main/listener/tcp/class_TcpListener.php +++ b/application/hub/main/listener/tcp/class_TcpListener.php @@ -4,7 +4,7 @@ * * @author Roland Haeder * @version 0.0.0 - * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 Hub Developer Team + * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2012 Hub Developer Team * @license GNU GPL 3.0 or any newer version * @link http://www.ship-simu.org * @@ -30,6 +30,9 @@ class TcpListener extends BaseListener implements Listenable { protected function __construct () { // Call parent constructor parent::__construct(__CLASS__); + + // Set the protocol to TCP + $this->setProtocol('tcp'); } /** @@ -38,16 +41,13 @@ class TcpListener extends BaseListener implements Listenable { * @param $nodeInstance A NodeHelper instance * @return $listenerInstance An instance a prepared listener class */ - public final static function createTcpListener (NodeHelper $nodeInstance) { + public static final function createTcpListener (NodeHelper $nodeInstance) { // Get new instance $listenerInstance = new TcpListener(); // Set the application instance $listenerInstance->setNodeInstance($nodeInstance); - // Set the protocol to TCP - $listenerInstance->setProtocol('tcp'); - // Return the prepared instance return $listenerInstance; } @@ -58,14 +58,14 @@ class TcpListener extends BaseListener implements Listenable { * @return void * @throws InvalidSocketException Thrown if the socket could not be initialized */ - public function initListener() { + public function initListener () { // Create a streaming socket, of type TCP/IP $mainSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); // 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); + throw new InvalidSocketException(array($this, $mainSocket), BaseListener::EXCEPTION_INVALID_SOCKET); } // END - if // Get socket error code for verification @@ -73,12 +73,19 @@ class TcpListener extends BaseListener implements Listenable { // Check if there was an error else if ($socketError > 0) { + // Handle this socket error with a faked recipientData array + $this->handleSocketError(__METHOD__, __LINE__, $mainSocket, array('0.0.0.0', '0')); + /* // Then throw again - throw new InvalidSocketException(array($this, gettype($mainSocket), $socketError, socket_strerror($socketError)), BaseListener::EXCEPTION_INVALID_SOCKET); + throw new InvalidSocketException(array($this, $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)) { + // Handle this socket error with a faked recipientData array + $this->handleSocketError(__METHOD__, __LINE__, $mainSocket, array('0.0.0.0', '0')); + /* // Get socket error code for verification $socketError = socket_last_error($mainSocket); @@ -89,13 +96,20 @@ class TcpListener extends BaseListener implements Listenable { $this->shutdownSocket($mainSocket); // And throw again - throw new InvalidSocketException(array($this, gettype($mainSocket), $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET); + throw new InvalidSocketException(array($this, $mainSocket, $socketError, $errorMessage), 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.. + /* + * "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.. + */ + self::createDebugInstance(__CLASS__)->debugOutput('TCP-LISTENER[' . __LINE__ . ']: Binding to address ' . $this->getListenAddress() . ':' . $this->getListenPort()); if (!socket_bind($mainSocket, $this->getListenAddress(), $this->getListenPort())) { + // Handle this socket error with a faked recipientData array + $this->handleSocketError(__METHOD__, __LINE__, $mainSocket, array('0.0.0.0', '0')); + /* // Get socket error code for verification $socketError = socket_last_error($mainSocket); @@ -106,11 +120,36 @@ class TcpListener extends BaseListener implements Listenable { $this->shutdownSocket($mainSocket); // And throw again - throw new InvalidSocketException(array($this, gettype($mainSocket), $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET); + throw new InvalidSocketException(array($this, $mainSocket, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET); + */ } // END - if // Start listen for connections + self::createDebugInstance(__CLASS__)->debugOutput('TCP-LISTENER[' . __LINE__ . ']: Listening for connections.'); if (!socket_listen($mainSocket)) { + // Handle this socket error with a faked recipientData array + $this->handleSocketError(__METHOD__, __LINE__, $mainSocket, array('0.0.0.0', '0')); + /* + // Get socket error code for verification + $socketError = socket_last_error($mainSocket); + + // Get error message + $errorMessage = socket_strerror($socketError); + + // Shutdown this socket + $this->shutdownSocket($mainSocket); + + // And throw again + throw new InvalidSocketException(array($this, $mainSocket, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET); + */ + } // END - if + + // Now, we want non-blocking mode + self::createDebugInstance(__CLASS__)->debugOutput('TCP-LISTENER[' . __LINE__ . ']: Setting non-blocking mode.'); + if (!socket_set_nonblock($mainSocket)) { + // Handle this socket error with a faked recipientData array + $this->handleSocketError(__METHOD__, __LINE__, $mainSocket, array('0.0.0.0', '0')); + /* // Get socket error code for verification $socketError = socket_last_error($mainSocket); @@ -121,17 +160,18 @@ class TcpListener extends BaseListener implements Listenable { $this->shutdownSocket($mainSocket); // And throw again - throw new InvalidSocketException(array($this, gettype($mainSocket), $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET); + throw new InvalidSocketException(array($this, $mainSocket, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET); + */ } // END - if // Set the main socket - $this->setSocketResource($mainSocket); + $this->registerServerSocketResource($mainSocket); - // Initialize the client pool instance - $poolInstance = ObjectFactory::createObjectByConfiguredName('client_pool_class', array($this)); + // Initialize the peer pool instance + $poolInstance = ObjectFactory::createObjectByConfiguredName('node_pool_class', array($this)); // Add main socket - $poolInstance->addClient($mainSocket); + $poolInstance->addPeer($mainSocket, BaseConnectionHelper::CONNECTION_TYPE_SERVER); // And add it to this listener $this->setPoolInstance($poolInstance); @@ -144,28 +184,28 @@ class TcpListener extends BaseListener implements Listenable { $this->setIteratorInstance($iteratorInstance); // Initialize the network package handler - $packageInstance = ObjectFactory::createObjectByConfiguredName('tcp_network_package_handler_class'); + $handlerInstance = ObjectFactory::createObjectByConfiguredName('tcp_raw_data_handler_class'); // Set it in this class - $this->setPackageInstance($packageInstance); + $this->setHandlerInstance($handlerInstance); // Output message - $this->debugOutput('LISTENER: TCP listener now ready on IP ' . $this->getListenAddress() . ', port ' . $this->getListenPort() . ' for service.'); + self::createDebugInstance(__CLASS__)->debugOutput('TCP-LISTENER[' . __LINE__ . ']: TCP listener now ready on IP ' . $this->getListenAddress() . ', port ' . $this->getListenPort() . ' for service.'); } /** * "Listens" for incoming network packages * * @return void - * @todo 0% done + * @throws InvalidSocketException If an invalid socket resource has been found */ public function doListen () { // Get all readers - $readers = $this->getPoolInstance()->getAllSockets(); + $readers = $this->getPoolInstance()->getAllSingleSockets(); $writers = array(); $excepts = array(); - // Check if we have some clients left + // Check if we have some peers left $left = socket_select( $readers, $writers, @@ -174,22 +214,87 @@ class TcpListener extends BaseListener implements Listenable { 150 ); - // Some new clients found? + // Some new peers found? if ($left < 1) { + // Debug message + //* EXTREME-NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('TCP-LISTENER[' . __LINE__ . ']: left=' . $left . ',serverSocket=' . $this->getSocketResource() . ',readers=' . print_r($readers, true)); + // Nothing new found return; } // END - if - // Do we have changed clients? + // Debug message + //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('TCP-LISTENER[' . __LINE__ . ']: serverSocket=' . $this->getSocketResource() . ',readers=' . print_r($readers, true)); + + // Do we have changed peers? if (in_array($this->getSocketResource(), $readers)) { - // Then accept it + /* + * Then accept it, if this socket is set to non-blocking IO and the + * connection is NOT sending any data, socket_read() may throw + * error 11 (Resource temporary unavailable). This really nasty + * because if you have blocking IO socket_read() will wait and wait + * and wait ... + */ $newSocket = socket_accept($this->getSocketResource()); // Debug message - $this->debugOutput('LISTENER: Adding new client: ' . $newSocket); - - // Add it to the clients - $this->getPoolInstance()->addClient($newSocket); + /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: newSocket=' . $newSocket . ',serverSocket=' .$this->getSocketResource()); + + // Array for timeout settings + $options = array( + // Seconds + 'sec' => $this->getConfigInstance()->getConfigEntry('tcp_socket_accept_wait_sec'), + // Milliseconds + 'usec' => $this->getConfigInstance()->getConfigEntry('tcp_socket_accept_wait_usec') + ); + + // Set timeout to configured seconds + // @TODO Does this work on Windozer boxes??? + if (!socket_set_option($newSocket, SOL_SOCKET, SO_RCVTIMEO, $options)) { + // Handle this socket error with a faked recipientData array + $this->handleSocketError(__METHOD__, __LINE__, $newSocket, array('0.0.0.0', '0')); + } // END - if + + // Output result (only for debugging!) + /* + $option = socket_get_option($newSocket, SOL_SOCKET, SO_RCVTIMEO); + self::createDebugInstance(__CLASS__)->debugOutput('SO_RCVTIMEO[' . gettype($option) . ']=' . print_r($option, true)); + */ + + // Enable SO_OOBINLINE + if (!socket_set_option($newSocket, SOL_SOCKET, SO_OOBINLINE ,1)) { + // Handle this socket error with a faked recipientData array + $this->handleSocketError(__METHOD__, __LINE__, $newSocket, array('0.0.0.0', '0')); + } // END - if + + // Set non-blocking + if (!socket_set_nonblock($newSocket)) { + // Handle this socket error with a faked recipientData array + $this->handleSocketError(__METHOD__, __LINE__, $newSocket, array('0.0.0.0', '0')); + } // END - if + + // Add it to the peers + $this->getPoolInstance()->addPeer($newSocket, BaseConnectionHelper::CONNECTION_TYPE_INCOMING); + + // Get peer name + if (!socket_getpeername($newSocket, $peerName)) { + // Handle this socket error with a faked recipientData array + $this->handleSocketError(__METHOD__, __LINE__, $newSocket, array('0.0.0.0', '0')); + } // END - if + + // Create a faked package data array + $packageData = array( + NetworkPackage::PACKAGE_DATA_SENDER => $peerName . ':0', + NetworkPackage::PACKAGE_DATA_RECIPIENT => $this->getSessionId(), + NetworkPackage::PACKAGE_DATA_PROTOCOL => $this->getProtocol(), + NetworkPackage::PACKAGE_DATA_STATUS => NetworkPackage::PACKAGE_STATUS_FAKED + ); + + // Get a socket registry + $registryInstance = SocketRegistryFactory::createSocketRegistryInstance(); + + // Register the socket with the registry and with the faked array + $registryInstance->registerSocket($this, $newSocket, $packageData); } // END - if // Do we have to rewind? @@ -199,14 +304,30 @@ class TcpListener extends BaseListener implements Listenable { } // END - if // Get the current value - $current = $this->getIteratorInstance()->current(); + $currentSocket = $this->getIteratorInstance()->current(); - // Handle it here - $this->getPackageInstance()->processResourcePackage($current); + // Handle it here, if not main server socket + //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__)->debugOutput('TCP-LISTENER[' . __LINE__ . ']: currentSocket=' . $currentSocket[BasePool::SOCKET_ARRAY_RESOURCE] . ',type=' . $currentSocket[BasePool::SOCKET_ARRAY_CONN_TYPE] . ',serverSocket=' . $this->getSocketResource()); + if (($currentSocket[BasePool::SOCKET_ARRAY_CONN_TYPE] != BaseConnectionHelper::CONNECTION_TYPE_SERVER) && ($currentSocket[BasePool::SOCKET_ARRAY_RESOURCE] != $this->getSocketResource())) { + // ... or else it will raise warnings like 'Transport endpoint is not connected' + $this->getHandlerInstance()->processRawDataFromResource($currentSocket); + } // END - if - // Advance to next entry. This should be the last line + // Advance to next entry. This should be the last line. $this->getIteratorInstance()->next(); } + + /** + * Checks whether the listener would accept the given package data array + * + * @param $packageData Raw package data + * @return $accepts Whether this listener does accept + * @throws UnsupportedOperationException If this method is called + */ + public function ifListenerAcceptsPackageData (array $packageData) { + // Please don't call this + throw new UnsupportedOperationException(array($this, __FUNCTION__), self::EXCEPTION_UNSPPORTED_OPERATION); + } } // [EOF]