From de07709bb94ea0540abec8d2d97fab0c0a871956 Mon Sep 17 00:00:00 2001 From: Dominique Fournier Date: Sat, 2 May 2020 13:43:37 +0000 Subject: [PATCH] tcpserver : Add all the methods allowed to be overrided by the user git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@5976 bf3deb0d-5f1a-0410-827f-c0cc1f45334c --- tcpserver.php | 275 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 213 insertions(+), 62 deletions(-) diff --git a/tcpserver.php b/tcpserver.php index fd9acab..994ce5f 100644 --- a/tcpserver.php +++ b/tcpserver.php @@ -34,19 +34,18 @@ class tcpserver /** Store the ports */ private $ports = array (); - /** The max number of children. The maximum of concurrent connections */ private $maxChild = 500; - + /** The timeout if not used a channel + */ + private $timeout = 30; /** Set to true in parent, and false in child */ private $parent = true; - /** The socket in the child */ private $socket = null; - /** Read in Text mode or in Binary mode */ private $readMode = "text"; @@ -61,25 +60,39 @@ class tcpserver private $pidLoopInBackground; /** Server name displayed in process list */ - private $processName; + private $processName = "tcpserver"; // }}} //////////////////////// // PUBLIC METHODS // //////////////////////// + /** The constructor add the error handler needed to catch the error on + * stream_select () when the process is killed + */ public function __construct () // {{{ { - $this->processName = "tcpserver"; + $this->logDebug ("NEW OBJECT CONSTRUCTED"); + set_error_handler([$this, 'errorHandler']); + } + // }}} + + /** The destructor is a log for debug + */ + public function __destruct () + // {{{ + { + $this->logDebug ("Object destructed"); } // }}} /** Set/get the max children, the maximum of concurrent connections * @param integer|null $val The number of child to get/set */ - public function maxChild ($val = null) + final public function maxChild ($val = null) // {{{ { + $this->logMethods (__METHOD__, func_get_args ()); if ($val === null) return $this->maxChild; $this->maxChild = intval ($val); @@ -88,26 +101,29 @@ class tcpserver // }}} /** Set/get the read mode : text or binary - * @param string|null $readMode The mode to set (or get if null) + * @param string|null $val The mode to set (or get if null) */ - public function readMode ($readMode = null) + final public function readMode ($val = null) // {{{ { - if ($readMode === null) + $this->logMethods (__METHOD__, func_get_args ()); + if ($val === null) return $this->readMode; - if ($readMode !== "text" && $readMode !== "binary") + if ($val !== "text" && $val !== "binary") throw new \Exception ("Invalid readMode provided (nor text nor binary)", 500); - $this->readMode = $readMode; + $this->readMode = $val; return $this; } // }}} /** Set the process name displayed in system + * @param string|null $val The name of the process to set (or get if null) */ - public function processName ($val = null) + final public function processName ($val = null) // {{{ { + $this->logMethods (__METHOD__, func_get_args ()); if ($val === null) return $this->processName; $this->processName = $val; @@ -115,6 +131,26 @@ class tcpserver } // }}} + /** Set the timeout for open communication without any data transmited. + * @param string|null $val The number of seconds to set (or get if null) + */ + final public function timeout ($val = null) + // {{{ + { + $this->logMethods (__METHOD__, func_get_args ()); + if ($val === null) + return $this->timeout; + if (! is_int ($val)) + throw new \Exception (dgettext ("domframework", + "tcpserver : invalid timeout provided : not integer value"), 500); + if ($val <= 0) + throw new \Exception (dgettext ("domframework", + "tcpserver : invalid timeout provided : negatif or null value"), 500); + $this->timeout = intval ($val); + return $this; + } + // }}} + /** Set the address, port and handler that will be enabled by loop * @param string $address The server address (can be 0.0.0.0 for all IPv4 * interfaces or :: for all the IPv4 and IPv6 interfaces) @@ -122,9 +158,10 @@ class tcpserver * @param callable $handler The handler that will be called when a client is * connected to the address:port */ - public function init ($address, $port, $handler) + final public function init ($address, $port, $handler) // {{{ { + $this->logMethods (__METHOD__, func_get_args ()); $this->nbChild = 0; $this->handlers["$address:$port"] = $handler; $this->addresses[] = $address; @@ -135,14 +172,16 @@ class tcpserver /** Start the main loop after the init and keep in it until loopStop */ - public function loop () + final public function loop () // {{{ { - declare(ticks = 10); + $this->logMethods (__METHOD__, func_get_args ()); + $this->logDebug ("Initialize the signal managers"); + //declare (ticks = 1); pcntl_async_signals (true); - pcntl_signal (SIGCHLD,[$this, "sigCHLD"]); - pcntl_signal (SIGTERM,[$this, "sigTERMINT"]); - pcntl_signal (SIGINT,[$this, "sigTERMINT"]); + pcntl_signal (SIGCHLD, [$this, "sigCHLD"]); + pcntl_signal (SIGTERM, [$this, "sigTERMINT"]); + pcntl_signal (SIGINT, [$this, "sigTERMINT"]); cli_set_process_title ($this->processName." main"); foreach ($this->addresses as $key => $address) { @@ -159,30 +198,27 @@ class tcpserver { throw new \Exception ("Can't create socket : $errstr"); } - if ($this->debug) - printf ("[".posix_getpid ()."] Listening on %s:%d...\n", - $address, $port); + stream_set_timeout ($sockServer[$key], $this->timeout); + $this->logDebug ("Listening on $address:$port..."); } $ppid = posix_getppid (); while (1) { if (posix_getppid () !== $ppid) { - echo "PARENT PID change : ".posix_getppid () ."!== $ppid : STOP\n"; - $this->loopStop (); + $this->logDebug ("PARENT PID change : ".posix_getppid () . + " !== $ppid : STOP"); + exit; } if ($this->loopStop) { - if ($this->debug) - echo "[".posix_getpid ()."] Do not accept new connections ". - "($this->nbChild)\n"; + $this->logDebug ("Do not accept new connections ($this->nbChild)"); foreach ($sockServer as $socket) stream_socket_shutdown ($socket, STREAM_SHUT_RD); if ($this->nbChild === 0) { - if ($this->debug) - echo "[".posix_getpid ()."] No more childs and loopStop requested". - " : end of process\n"; + $this->logDebug ("No more childs and loopStop requested". + " : end of process"); exit; } sleep (2); @@ -232,10 +268,9 @@ class tcpserver $address = substr ($address, 8, -1); if (substr ($localAddress, 0, 7) === "[::ffff:") $localAddress = substr ($localAddress, 8, -1); - if ($this->debug) - echo "[".posix_getpid ()."] New connection $address:$port > ". + $this->logDebug ("New connection $address:$port > ". "$localAddress:$localPort : ". - "use handler $handlerAddress:$localPort\n"; + "use handler $handlerAddress:$localPort"); $handler = $this->handlers["$handlerAddress:$localPort"]; // We got a connection fork to manage it if ($this->nbChild > $this->maxChild) @@ -257,9 +292,8 @@ class tcpserver continue; } - if ($this->debug) - echo "[".posix_getpid ()."] Child start ($address:$port) : ". - ($this->nbChild+1)." child active\n"; + $this->logDebug ("Child start ($address:$port) : ". + ($this->nbChild+1)." child active"); // Do not stop if the parent is requesting a stop from Ctrl+C cli_set_process_title ($this->processName." ($address:$port)"); pcntl_signal(SIGINT, SIG_IGN); @@ -279,8 +313,7 @@ class tcpserver $function = $handler; $function ($this); } - if ($this->debug) - echo "[".posix_getpid ()."] Child ended ($address:$port)\n"; + $this->logDebug ("Child ended ($address:$port)"); exit; } } @@ -291,9 +324,10 @@ class tcpserver * end of the existing processus * Block until all is closed */ - public function loopStop () + final public function loopStop () // {{{ { + $this->logMethods (__METHOD__, func_get_args ()); $this->loopStop = true; } // }}} @@ -301,11 +335,11 @@ class tcpserver /** Start the main loop in background and do not wait its end * @return the PID of the child */ - public function loopInBackgroundStart () + final public function loopInBackgroundStart () // {{{ { - if ($this->debug) - echo "[".posix_getpid ()."] Start loopInBackground\n"; + $this->logMethods (__METHOD__, func_get_args ()); + $this->logDebug ("Start loopInBackground"); $pid = pcntl_fork(); $this->pidLoopInBackground = $pid; if ($pid == -1) @@ -314,9 +348,8 @@ class tcpserver } else if ($pid) { - if ($this->debug) - echo "[".posix_getpid ()."] loopInBackground : child = ". - $this->pidLoopInBackground."\n"; + $this->logDebug ("loopInBackground : child = ". + $this->pidLoopInBackground); // parent process: return to the main loop return $pid; } @@ -334,22 +367,24 @@ class tcpserver /** Stop the main loop in background and wait until its end */ - public function loopInBackgroundStop () + final public function loopInBackgroundStop () // {{{ { - if ($this->debug) - echo "[".posix_getpid ()."] Request loopInBackgroundStop\n"; + $this->logMethods (__METHOD__, func_get_args ()); + $this->logDebug ("Request loopInBackgroundStop"); posix_kill ($this->pidLoopInBackground, SIGINT); pcntl_waitpid ($this->pidLoopInBackground, $status); + $this->logDebug ("Request loopInBackgroundStop : END"); } // }}} /** In child, get the socket to direct access * @return resource The socket with the client */ - public function getSock () + final public function getSock () // {{{ { + $this->logMethods (__METHOD__, func_get_args ()); if ($this->parent === true) throw new \Exception ("Can not return the socket in parent mode", 500); if ($this->socket === null) @@ -363,9 +398,10 @@ class tcpserver * @return array array ("peer address", peer port, "local address", local * port) */ - public function getInfo () + final public function getInfo () // {{{ { + $this->logMethods (__METHOD__, func_get_args ()); if ($this->parent === true) throw new \Exception ("Can not get info in parent mode", 500); if ($this->socket === null) @@ -391,10 +427,11 @@ class tcpserver * @param boolean $val True to activate, false to disable SSL * @param integer $cryptoMethod The cryptoMethod allowed */ - public function cryptoEnable ($val, + final public function cryptoEnable ($val, $cryptoMethod = STREAM_CRYPTO_METHOD_TLS_SERVER) // {{{ { + $this->logMethods (__METHOD__, func_get_args ()); if ($this->socket === null) throw new \Exception ("Can not send to server $this->ipOrName : ". "The server is not connected", 500); @@ -412,9 +449,10 @@ class tcpserver /** Set context SSL option. * @param array $options The ssl array to set */ - public function setSSLOptions ($options) + final public function setSSLOptions ($options) // {{{ { + $this->logMethods (__METHOD__, func_get_args ()); if ($this->socket === null) throw new \Exception ("Can not send to server $this->ipOrName : ". "The server is not connected", 500); @@ -426,17 +464,21 @@ class tcpserver * @param mixed $data The data to send * @return the length of data sent */ - public function send ($data) + final public function send ($data) // {{{ { + $this->logMethods (__METHOD__, func_get_args ()); if ($this->parent === true) throw new \Exception ("Can not send data in parent mode", 500); if ($this->socket === null) throw new \Exception ("Can not send to client : not connected", 500); $length = strlen ($data); - $sentLen = @fwrite ($this->socket, $data); +ob_start (); + $sentLen = fwrite ($this->socket, $data); +$this->debug (ob_get_flush ()); if ($sentLen < $length) throw new \Exception ("Can not send data to client", 500); + $this->logSend ($data); return $sentLen; } // }}} @@ -448,9 +490,10 @@ class tcpserver * @param integer $maxLength Limit the length of the data from the server * @return The content */ - public function read ($maxLength = 1024) + final public function read ($maxLength = 1024) // {{{ { + $this->logMethods (__METHOD__, func_get_args ()); if ($this->parent === true) throw new \Exception ("Can not read data in parent mode", 500); if ($this->socket === null) @@ -469,15 +512,17 @@ class tcpserver throw new \Exception ("Can not read from client : ". error_get_last ()["message"], 500); } + $this->logReceive ($data); return $read; } // }}} /** Disconnect the socket */ - public function disconnect () + final public function disconnect () // {{{ { + $this->logMethods (__METHOD__, func_get_args ()); if ($this->parent === true) throw new \Exception ("Can not send data in parent mode", 500); if ($this->socket === null) @@ -487,6 +532,114 @@ class tcpserver } // }}} + /** Log the data send to the client. By default, do nothing, but can be + * overrided by the user + * @param string $data The data to store in log + */ + public function logSend ($data) + // {{{ + { + if (! $this->debug) + return; + $data = rtrim ($data)."\n"; + file_put_contents ("/tmp/debug", date ("H:i:s")." [".posix_getpid (). + "] S> $data", FILE_APPEND); + } + // }}} + + /** Log the data received from the client. By default, do nothing, but can be + * overrided by the user + * @param string $data The data to store in log + */ + public function logReceive ($data) + // {{{ + { + if (! $this->debug) + return; + $data = rtrim ($data)."\n"; + file_put_contents ("/tmp/debug", date ("H:i:s")." [".posix_getpid (). + "] C> $data", FILE_APPEND); + } + // }}} + + /** Log the methods called. By default, do nothing, but can be overrided by + * the user + * @param string $method The data to store in log + * @param mixed|null $args The data to store in log + */ + public function logMethods ($method, $args) + // {{{ + { + if (! $this->debug) + return; + $data = $method." ("; + foreach ($args as $key => $arg) + { + if ($key > 0) + $data .= ", "; + if (is_string ($arg)) + $data .= "\"$arg\""; + elseif (is_numeric ($arg)) + $data .= "$arg"; + elseif ($arg === false) + $data .= "false"; + elseif ($arg === true) + $data .= "true"; + elseif ($arg === null) + $data .= "null"; + elseif (is_array ($arg)) + $data .= "['".implode ("','", $arg."']"); + elseif (is_object ($arg)) + $data .= ""; + else + $data .= "UNKNOWN : ".gettype ($arg); + } + $data.= ")\n"; + file_put_contents ("/tmp/debug", date ("H:i:s")." [".posix_getpid (). + "] METHOD $data", FILE_APPEND); + } + // }}} + + /** Log the debug, By defaul do nothing, but can be overrided by the user + * @param mixed|null $params The data to store in log + */ + public function logDebug ($params) + // {{{ + { + if (! $this->debug) + return; + file_put_contents ("/tmp/debug", date ("H:i:s")." [".posix_getpid (). + "] DEBUG $params\n", FILE_APPEND); + } + // }}} + + /** Log the errors, By defaul do nothing, but can be overrided by the user + * @param mixed|null $params The data to store in log + */ + public function logError ($params) + // {{{ + { + if (! $this->debug) + return; + file_put_contents ("/tmp/debug", date ("H:i:s")." [".posix_getpid (). + "] ERROR $params\n", FILE_APPEND); + } + // }}} + + /** Error catcher. + * By default, do nothing, but can be overrided by the user + * @param integer $errNo The error number + * @param string $errMsg The error message + * @param string $file The file name + * @param integer $line The line where the error raised + */ + public function errorHandler ($errNo, $errMsg, $file, $line) + // {{{ + { + $this->logError ("line $line : $errMsg"); + } + // }}} + ///////////////////////// // PRIVATE METHODS // ///////////////////////// @@ -496,12 +649,11 @@ class tcpserver // {{{ { $this->nbChild --; - if ($this->debug) - echo "[".posix_getpid ()."] One child finished : $this->nbChild childs ". - "remain active\n"; + $this->logDebug ("One child finished : $this->nbChild childs remain ". + "active"); //pcntl_wait ($status, WNOHANG); $rc = pcntl_wait ($status); -echo "END : rc\n"; + $this->logDebug ( "One child finished : $rc"); } // }}} @@ -512,8 +664,7 @@ echo "END : rc\n"; private function sigTERMINT () // {{{ { - if ($this->debug) - echo "[".posix_getpid ()."] Request TERM/INT : Wait for last childs\n"; + $this->logDebug ("Request TERM/INT : Wait for last childs"); $this->loopStop (); } // }}}