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
This commit is contained in:
2020-05-02 13:43:37 +00:00
parent f9dd03bde1
commit de07709bb9

View File

@@ -34,19 +34,18 @@ class tcpserver
/** Store the ports /** Store the ports
*/ */
private $ports = array (); private $ports = array ();
/** The max number of children. The maximum of concurrent connections /** The max number of children. The maximum of concurrent connections
*/ */
private $maxChild = 500; private $maxChild = 500;
/** The timeout if not used a channel
*/
private $timeout = 30;
/** Set to true in parent, and false in child /** Set to true in parent, and false in child
*/ */
private $parent = true; private $parent = true;
/** The socket in the child /** The socket in the child
*/ */
private $socket = null; private $socket = null;
/** Read in Text mode or in Binary mode /** Read in Text mode or in Binary mode
*/ */
private $readMode = "text"; private $readMode = "text";
@@ -61,25 +60,39 @@ class tcpserver
private $pidLoopInBackground; private $pidLoopInBackground;
/** Server name displayed in process list /** Server name displayed in process list
*/ */
private $processName; private $processName = "tcpserver";
// }}} // }}}
//////////////////////// ////////////////////////
// PUBLIC METHODS // // PUBLIC METHODS //
//////////////////////// ////////////////////////
/** The constructor add the error handler needed to catch the error on
* stream_select () when the process is killed
*/
public function __construct () 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 /** Set/get the max children, the maximum of concurrent connections
* @param integer|null $val The number of child to get/set * @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) if ($val === null)
return $this->maxChild; return $this->maxChild;
$this->maxChild = intval ($val); $this->maxChild = intval ($val);
@@ -88,26 +101,29 @@ class tcpserver
// }}} // }}}
/** Set/get the read mode : text or binary /** 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; return $this->readMode;
if ($readMode !== "text" && $readMode !== "binary") if ($val !== "text" && $val !== "binary")
throw new \Exception ("Invalid readMode provided (nor text nor binary)", throw new \Exception ("Invalid readMode provided (nor text nor binary)",
500); 500);
$this->readMode = $readMode; $this->readMode = $val;
return $this; return $this;
} }
// }}} // }}}
/** Set the process name displayed in system /** 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) if ($val === null)
return $this->processName; return $this->processName;
$this->processName = $val; $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 /** 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 * @param string $address The server address (can be 0.0.0.0 for all IPv4
* interfaces or :: for all the IPv4 and IPv6 interfaces) * 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 * @param callable $handler The handler that will be called when a client is
* connected to the address:port * 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->nbChild = 0;
$this->handlers["$address:$port"] = $handler; $this->handlers["$address:$port"] = $handler;
$this->addresses[] = $address; $this->addresses[] = $address;
@@ -135,14 +172,16 @@ class tcpserver
/** Start the main loop after the init and keep in it until loopStop /** 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_async_signals (true);
pcntl_signal (SIGCHLD,[$this, "sigCHLD"]); pcntl_signal (SIGCHLD, [$this, "sigCHLD"]);
pcntl_signal (SIGTERM,[$this, "sigTERMINT"]); pcntl_signal (SIGTERM, [$this, "sigTERMINT"]);
pcntl_signal (SIGINT,[$this, "sigTERMINT"]); pcntl_signal (SIGINT, [$this, "sigTERMINT"]);
cli_set_process_title ($this->processName." main"); cli_set_process_title ($this->processName." main");
foreach ($this->addresses as $key => $address) foreach ($this->addresses as $key => $address)
{ {
@@ -159,30 +198,27 @@ class tcpserver
{ {
throw new \Exception ("Can't create socket : $errstr"); throw new \Exception ("Can't create socket : $errstr");
} }
if ($this->debug) stream_set_timeout ($sockServer[$key], $this->timeout);
printf ("[".posix_getpid ()."] Listening on %s:%d...\n", $this->logDebug ("Listening on $address:$port...");
$address, $port);
} }
$ppid = posix_getppid (); $ppid = posix_getppid ();
while (1) while (1)
{ {
if (posix_getppid () !== $ppid) if (posix_getppid () !== $ppid)
{ {
echo "PARENT PID change : ".posix_getppid () ."!== $ppid : STOP\n"; $this->logDebug ("PARENT PID change : ".posix_getppid () .
$this->loopStop (); " !== $ppid : STOP");
exit;
} }
if ($this->loopStop) if ($this->loopStop)
{ {
if ($this->debug) $this->logDebug ("Do not accept new connections ($this->nbChild)");
echo "[".posix_getpid ()."] Do not accept new connections ".
"($this->nbChild)\n";
foreach ($sockServer as $socket) foreach ($sockServer as $socket)
stream_socket_shutdown ($socket, STREAM_SHUT_RD); stream_socket_shutdown ($socket, STREAM_SHUT_RD);
if ($this->nbChild === 0) if ($this->nbChild === 0)
{ {
if ($this->debug) $this->logDebug ("No more childs and loopStop requested".
echo "[".posix_getpid ()."] No more childs and loopStop requested". " : end of process");
" : end of process\n";
exit; exit;
} }
sleep (2); sleep (2);
@@ -232,10 +268,9 @@ class tcpserver
$address = substr ($address, 8, -1); $address = substr ($address, 8, -1);
if (substr ($localAddress, 0, 7) === "[::ffff:") if (substr ($localAddress, 0, 7) === "[::ffff:")
$localAddress = substr ($localAddress, 8, -1); $localAddress = substr ($localAddress, 8, -1);
if ($this->debug) $this->logDebug ("New connection $address:$port > ".
echo "[".posix_getpid ()."] New connection $address:$port > ".
"$localAddress:$localPort : ". "$localAddress:$localPort : ".
"use handler $handlerAddress:$localPort\n"; "use handler $handlerAddress:$localPort");
$handler = $this->handlers["$handlerAddress:$localPort"]; $handler = $this->handlers["$handlerAddress:$localPort"];
// We got a connection fork to manage it // We got a connection fork to manage it
if ($this->nbChild > $this->maxChild) if ($this->nbChild > $this->maxChild)
@@ -257,9 +292,8 @@ class tcpserver
continue; continue;
} }
if ($this->debug) $this->logDebug ("Child start ($address:$port) : ".
echo "[".posix_getpid ()."] Child start ($address:$port) : ". ($this->nbChild+1)." child active");
($this->nbChild+1)." child active\n";
// Do not stop if the parent is requesting a stop from Ctrl+C // Do not stop if the parent is requesting a stop from Ctrl+C
cli_set_process_title ($this->processName." ($address:$port)"); cli_set_process_title ($this->processName." ($address:$port)");
pcntl_signal(SIGINT, SIG_IGN); pcntl_signal(SIGINT, SIG_IGN);
@@ -279,8 +313,7 @@ class tcpserver
$function = $handler; $function = $handler;
$function ($this); $function ($this);
} }
if ($this->debug) $this->logDebug ("Child ended ($address:$port)");
echo "[".posix_getpid ()."] Child ended ($address:$port)\n";
exit; exit;
} }
} }
@@ -291,9 +324,10 @@ class tcpserver
* end of the existing processus * end of the existing processus
* Block until all is closed * Block until all is closed
*/ */
public function loopStop () final public function loopStop ()
// {{{ // {{{
{ {
$this->logMethods (__METHOD__, func_get_args ());
$this->loopStop = true; $this->loopStop = true;
} }
// }}} // }}}
@@ -301,11 +335,11 @@ class tcpserver
/** Start the main loop in background and do not wait its end /** Start the main loop in background and do not wait its end
* @return the PID of the child * @return the PID of the child
*/ */
public function loopInBackgroundStart () final public function loopInBackgroundStart ()
// {{{ // {{{
{ {
if ($this->debug) $this->logMethods (__METHOD__, func_get_args ());
echo "[".posix_getpid ()."] Start loopInBackground\n"; $this->logDebug ("Start loopInBackground");
$pid = pcntl_fork(); $pid = pcntl_fork();
$this->pidLoopInBackground = $pid; $this->pidLoopInBackground = $pid;
if ($pid == -1) if ($pid == -1)
@@ -314,9 +348,8 @@ class tcpserver
} }
else if ($pid) else if ($pid)
{ {
if ($this->debug) $this->logDebug ("loopInBackground : child = ".
echo "[".posix_getpid ()."] loopInBackground : child = ". $this->pidLoopInBackground);
$this->pidLoopInBackground."\n";
// parent process: return to the main loop // parent process: return to the main loop
return $pid; return $pid;
} }
@@ -334,22 +367,24 @@ class tcpserver
/** Stop the main loop in background and wait until its end /** Stop the main loop in background and wait until its end
*/ */
public function loopInBackgroundStop () final public function loopInBackgroundStop ()
// {{{ // {{{
{ {
if ($this->debug) $this->logMethods (__METHOD__, func_get_args ());
echo "[".posix_getpid ()."] Request loopInBackgroundStop\n"; $this->logDebug ("Request loopInBackgroundStop");
posix_kill ($this->pidLoopInBackground, SIGINT); posix_kill ($this->pidLoopInBackground, SIGINT);
pcntl_waitpid ($this->pidLoopInBackground, $status); pcntl_waitpid ($this->pidLoopInBackground, $status);
$this->logDebug ("Request loopInBackgroundStop : END");
} }
// }}} // }}}
/** In child, get the socket to direct access /** In child, get the socket to direct access
* @return resource The socket with the client * @return resource The socket with the client
*/ */
public function getSock () final public function getSock ()
// {{{ // {{{
{ {
$this->logMethods (__METHOD__, func_get_args ());
if ($this->parent === true) if ($this->parent === true)
throw new \Exception ("Can not return the socket in parent mode", 500); throw new \Exception ("Can not return the socket in parent mode", 500);
if ($this->socket === null) if ($this->socket === null)
@@ -363,9 +398,10 @@ class tcpserver
* @return array array ("peer address", peer port, "local address", local * @return array array ("peer address", peer port, "local address", local
* port) * port)
*/ */
public function getInfo () final public function getInfo ()
// {{{ // {{{
{ {
$this->logMethods (__METHOD__, func_get_args ());
if ($this->parent === true) if ($this->parent === true)
throw new \Exception ("Can not get info in parent mode", 500); throw new \Exception ("Can not get info in parent mode", 500);
if ($this->socket === null) if ($this->socket === null)
@@ -391,10 +427,11 @@ class tcpserver
* @param boolean $val True to activate, false to disable SSL * @param boolean $val True to activate, false to disable SSL
* @param integer $cryptoMethod The cryptoMethod allowed * @param integer $cryptoMethod The cryptoMethod allowed
*/ */
public function cryptoEnable ($val, final public function cryptoEnable ($val,
$cryptoMethod = STREAM_CRYPTO_METHOD_TLS_SERVER) $cryptoMethod = STREAM_CRYPTO_METHOD_TLS_SERVER)
// {{{ // {{{
{ {
$this->logMethods (__METHOD__, func_get_args ());
if ($this->socket === null) if ($this->socket === null)
throw new \Exception ("Can not send to server $this->ipOrName : ". throw new \Exception ("Can not send to server $this->ipOrName : ".
"The server is not connected", 500); "The server is not connected", 500);
@@ -412,9 +449,10 @@ class tcpserver
/** Set context SSL option. /** Set context SSL option.
* @param array $options The ssl array to set * @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) if ($this->socket === null)
throw new \Exception ("Can not send to server $this->ipOrName : ". throw new \Exception ("Can not send to server $this->ipOrName : ".
"The server is not connected", 500); "The server is not connected", 500);
@@ -426,17 +464,21 @@ class tcpserver
* @param mixed $data The data to send * @param mixed $data The data to send
* @return the length of data sent * @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) if ($this->parent === true)
throw new \Exception ("Can not send data in parent mode", 500); throw new \Exception ("Can not send data in parent mode", 500);
if ($this->socket === null) if ($this->socket === null)
throw new \Exception ("Can not send to client : not connected", 500); throw new \Exception ("Can not send to client : not connected", 500);
$length = strlen ($data); $length = strlen ($data);
$sentLen = @fwrite ($this->socket, $data); ob_start ();
$sentLen = fwrite ($this->socket, $data);
$this->debug (ob_get_flush ());
if ($sentLen < $length) if ($sentLen < $length)
throw new \Exception ("Can not send data to client", 500); throw new \Exception ("Can not send data to client", 500);
$this->logSend ($data);
return $sentLen; return $sentLen;
} }
// }}} // }}}
@@ -448,9 +490,10 @@ class tcpserver
* @param integer $maxLength Limit the length of the data from the server * @param integer $maxLength Limit the length of the data from the server
* @return The content * @return The content
*/ */
public function read ($maxLength = 1024) final public function read ($maxLength = 1024)
// {{{ // {{{
{ {
$this->logMethods (__METHOD__, func_get_args ());
if ($this->parent === true) if ($this->parent === true)
throw new \Exception ("Can not read data in parent mode", 500); throw new \Exception ("Can not read data in parent mode", 500);
if ($this->socket === null) if ($this->socket === null)
@@ -469,15 +512,17 @@ class tcpserver
throw new \Exception ("Can not read from client : ". throw new \Exception ("Can not read from client : ".
error_get_last ()["message"], 500); error_get_last ()["message"], 500);
} }
$this->logReceive ($data);
return $read; return $read;
} }
// }}} // }}}
/** Disconnect the socket /** Disconnect the socket
*/ */
public function disconnect () final public function disconnect ()
// {{{ // {{{
{ {
$this->logMethods (__METHOD__, func_get_args ());
if ($this->parent === true) if ($this->parent === true)
throw new \Exception ("Can not send data in parent mode", 500); throw new \Exception ("Can not send data in parent mode", 500);
if ($this->socket === null) 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 .= "<object>";
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 // // PRIVATE METHODS //
///////////////////////// /////////////////////////
@@ -496,12 +649,11 @@ class tcpserver
// {{{ // {{{
{ {
$this->nbChild --; $this->nbChild --;
if ($this->debug) $this->logDebug ("One child finished : $this->nbChild childs remain ".
echo "[".posix_getpid ()."] One child finished : $this->nbChild childs ". "active");
"remain active\n";
//pcntl_wait ($status, WNOHANG); //pcntl_wait ($status, WNOHANG);
$rc = pcntl_wait ($status); $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 () private function sigTERMINT ()
// {{{ // {{{
{ {
if ($this->debug) $this->logDebug ("Request TERM/INT : Wait for last childs");
echo "[".posix_getpid ()."] Request TERM/INT : Wait for last childs\n";
$this->loopStop (); $this->loopStop ();
} }
// }}} // }}}