X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=inc%2Fmain%2Fclasses%2Flistener%2Fclass_BaseListener.php;fp=inc%2Fmain%2Fclasses%2Flistener%2Fclass_BaseListener.php;h=bd807bd7f0abcffd9817554ec55608d981969a7e;hb=64c177abc79f904bc0e0cf78f83a939dfa5ef9e4;hp=5a5721353c7c11b98aebd638cfd138119468715b;hpb=1277bc312f5e5fe20eb9d4e5320cde4fadeadb5c;p=core.git diff --git a/inc/main/classes/listener/class_BaseListener.php b/inc/main/classes/listener/class_BaseListener.php index 5a572135..bd807bd7 100644 --- a/inc/main/classes/listener/class_BaseListener.php +++ b/inc/main/classes/listener/class_BaseListener.php @@ -286,6 +286,219 @@ class BaseListener extends BaseFrameworkSystem implements Visitable { public function monitorIncomingRawData () { throw new UnsupportedOperationException(array($this, __FUNCTION__), self::EXCEPTION_UNSPPORTED_OPERATION); } + + /** + * Constructs a callable method name from given socket error code. If the + * method is not found, a generic one is used. + * + * @param $errorCode Error code from socket_last_error() + * @return $handlerName Call-back method name for the error handler + * @throws UnsupportedSocketErrorHandlerException If the error handler is not implemented + */ + protected function getSocketErrorHandlerFromCode ($errorCode) { + // Create a name from translated error code + $handlerName = 'socketError' . self::convertToClassName($this->translateSocketErrorCodeToName($errorCode)) . 'Handler'; + + // Is the call-back method there? + if (!method_exists($this, $handlerName)) { + // Please implement this + throw new UnsupportedSocketErrorHandlerException(array($this, $handlerName, $errorCode), BaseConnectionHelper::EXCEPTION_UNSUPPORTED_ERROR_HANDLER); + } // END - if + + // Return it + return $handlerName; + } + + /** + * Handles socket error for given socket resource and peer data. This method + * validates $socketResource if it is a valid resource (see is_resource()) + * but assumes valid data in array $recipientData, except that + * count($recipientData) is always 2. + * + * @param $method Value of __METHOD__ from calling method + * @param $line Value of __LINE__ from calling method + * @param $socketResource A valid socket resource + * @param $socketData A valid socket data array (0 = IP/file name, 1 = port) + * @return void + * @throws InvalidSocketException If $socketResource is no socket resource + * @throws NoSocketErrorDetectedException If socket_last_error() gives zero back + */ + protected final function handleSocketError ($method, $line, $socketResource, array $socketData) { + // This method handles only socket resources + if (!is_resource($socketResource)) { + // No resource, abort here + throw new InvalidSocketException(array($this, $socketResource), BaseListener::EXCEPTION_INVALID_SOCKET); + } // END - if + + // Check socket array, 1st element is mostly IP address (or file name), 2nd is port number + //* DEBUG-DIE: */ die(__METHOD__ . ':socketData=' . print_r($socketData, TRUE)); + assert(isset($socketData[0])); + assert(isset($socketData[1])); + + // Get error code for first validation (0 is not an error) + $errorCode = socket_last_error($socketResource); + + // If the error code is zero, someone called this method without an error + if ($errorCode == 0) { + // No error detected (or previously cleared outside this method) + throw new NoSocketErrorDetectedException(array($this, $socketResource), BaseListener::EXCEPTION_NO_SOCKET_ERROR); + } // END - if + + // Get handler (method) name + $handlerName = $this->getSocketErrorHandlerFromCode($errorCode); + + // Call-back the error handler method + call_user_func_array(array($this, $handlerName), array($socketResource, $socketData)); + + // Finally clear the error because it has been handled + socket_clear_error($socketResource); + } + + /** + * Translates socket error codes into our own internal names which can be + * used for call-backs. + * + * @param $errorCode The error code from socket_last_error() to be translated + * @return $errorName The translated name (all lower-case, with underlines) + */ + public function translateSocketErrorCodeToName ($errorCode) { + // Nothing bad happened by default + $errorName = BaseRawDataHandler::SOCKET_CONNECTED; + + // Is the code a number, then we have to change it + switch ($errorCode) { + case 0: // Silently ignored, the socket is connected + break; + + case 11: // "Resource temporary unavailable" + $errorName = BaseRawDataHandler::SOCKET_ERROR_RESOURCE_UNAVAILABLE; + break; + + case 13: // "Permission denied" + $errorName = BaseRawDataHandler::SOCKET_ERROR_PERMISSION_DENIED; + break; + + case 32: // "Broken pipe" + $errorName = BaseRawDataHandler::SOCKET_ERROR_BROKEN_PIPE; + break; + + case 104: // "Connection reset by peer" + $errorName = BaseRawDataHandler::SOCKET_ERROR_CONNECTION_RESET_BY_PEER; + break; + + case 107: // "Transport end-point not connected" + case 134: // On some (?) systems for 'transport end-point not connected' + // @TODO On some systems it is 134, on some 107? + $errorName = BaseRawDataHandler::SOCKET_ERROR_TRANSPORT_ENDPOINT; + break; + + case 110: // "Connection timed out" + $errorName = BaseRawDataHandler::SOCKET_ERROR_CONNECTION_TIMED_OUT; + break; + + case 111: // "Connection refused" + $errorName = BaseRawDataHandler::SOCKET_ERROR_CONNECTION_REFUSED; + break; + + case 113: // "No route to host" + $errorName = BaseRawDataHandler::SOCKET_ERROR_NO_ROUTE_TO_HOST; + break; + + case 114: // "Operation already in progress" + $errorName = BaseRawDataHandler::SOCKET_ERROR_OPERATION_ALREADY_PROGRESS; + break; + + case 115: // "Operation now in progress" + $errorName = BaseRawDataHandler::SOCKET_ERROR_OPERATION_IN_PROGRESS; + break; + + default: // Everything else <> 0 + // Unhandled error code detected, so first debug it because we may want to handle it like the others + self::createDebugInstance(__CLASS__)->debugOutput('BASE-HUB[' . __METHOD__ . ':' . __LINE__ . '] UNKNOWN ERROR CODE = ' . $errorCode . ', MESSAGE = ' . socket_strerror($errorCode)); + + // Change it only in this class + $errorName = BaseRawDataHandler::SOCKET_ERROR_UNKNOWN; + break; + } + + // Return translated name + return $errorName; + } + + /** + * Shuts down a given socket resource. This method does only ease calling + * the right visitor. + * + * @param $socketResource A valid socket resource + * @return void + */ + public function shutdownSocket ($socketResource) { + // Debug message + self::createDebugInstance(__CLASS__)->debugOutput('HUB-SYSTEM: Shutting down socket resource ' . $socketResource . ' with state ' . $this->getPrintableState() . ' ...'); + + // Set socket resource + $this->setSocketResource($socketResource); + + // Get a visitor instance + $visitorInstance = ObjectFactory::createObjectByConfiguredName('shutdown_socket_visitor_class'); + + // Debug output + self::createDebugInstance(__CLASS__)->debugOutput('HUB-SYSTEM:' . $this->__toString() . ': visitorInstance=' . $visitorInstance->__toString()); + + // Call the visitor + $this->accept($visitorInstance); + } + + /** + * Half-shuts down a given socket resource. This method does only ease calling + * an other visitor than shutdownSocket() does. + * + * @param $socketResource A valid socket resource + * @return void + */ + public function halfShutdownSocket ($socketResource) { + // Debug message + self::createDebugInstance(__CLASS__)->debugOutput('HUB-SYSTEM: Half-shutting down socket resource ' . $socketResource . ' with state ' . $this->getPrintableState() . ' ...'); + + // Set socket resource + $this->setSocketResource($socketResource); + + // Get a visitor instance + $visitorInstance = ObjectFactory::createObjectByConfiguredName('half_shutdown_socket_visitor_class'); + + // Debug output + self::createDebugInstance(__CLASS__)->debugOutput('HUB-SYSTEM:' . $this->__toString() . ': visitorInstance=' . $visitorInstance->__toString()); + + // Call the visitor + $this->accept($visitorInstance); + } + + // ************************************************************************ + // Socket error handler call-back methods + // ************************************************************************ + + /** + * Handles socket error 'permission denied', but does not clear it for + * later debugging purposes. + * + * @param $socketResource A valid socket resource + * @param $socketData A valid socket data array (0 = IP/file name, 1 = port) + * @return void + * @throws SocketBindingException The socket could not be bind to + */ + protected function socketErrorPermissionDeniedHandler ($socketResource, array $socketData) { + // Get socket error code for verification + $socketError = socket_last_error($socketResource); + + // Get error message + $errorMessage = socket_strerror($socketError); + + // Shutdown this socket + $this->shutdownSocket($socketResource); + + // Throw it again + throw new SocketBindingException(array($this, $socketData, $socketResource, $socketError, $errorMessage), BaseListener::EXCEPTION_INVALID_SOCKET); + } } // [EOF]