716 lines
22 KiB
PHP
716 lines
22 KiB
PHP
<?php
|
|
/** DomFramework
|
|
* @package domframework
|
|
* @author Dominique Fournier <dominique@fournier38.fr>
|
|
* @license BSD
|
|
*/
|
|
|
|
namespace Domframework;
|
|
|
|
/** This class allow to start a TCP server and call a function each time a
|
|
* client is connected on it. Each client is separated in a child, so the
|
|
* server allow to have multiple simultaneous connections.
|
|
*
|
|
* The handler method or function will get one parameter : the client from
|
|
* socket_accept. It will allow to use socket_getpeername to get the peer
|
|
* address and port, socket_read to get the data from the client,
|
|
* socket_write to write on the client, socket_shutdown ($client) and
|
|
* socket_close ($client) to finish the connection
|
|
*
|
|
* The server has a child limit set to 500 connections by default
|
|
*/
|
|
class Tcpserver
|
|
{
|
|
////////////////////
|
|
// PROPERTIES //
|
|
////////////////////
|
|
// {{{
|
|
/** Allow to debug with messages on screen
|
|
*/
|
|
private $debug = false;
|
|
/** Store the data concerning the sockets and the handlers
|
|
*/
|
|
private $handlers = array ();
|
|
/** Store the addresses
|
|
*/
|
|
private $addresses = array ();
|
|
/** 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";
|
|
/** Stop the new connections
|
|
*/
|
|
private $loopStop = false;
|
|
/** The number of active clients
|
|
*/
|
|
private $nbChild = 0;
|
|
/** The PID number when loop in background is active
|
|
*/
|
|
private $pidLoopInBackground;
|
|
/** Server name displayed in process list
|
|
*/
|
|
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->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
|
|
*/
|
|
final public function maxChild ($val = null)
|
|
// {{{
|
|
{
|
|
$this->logMethods (__METHOD__, func_get_args ());
|
|
if ($val === null)
|
|
return $this->maxChild;
|
|
$this->maxChild = intval ($val);
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Set/get the read mode : text or binary
|
|
* @param string|null $val The mode to set (or get if null)
|
|
*/
|
|
final public function readMode ($val = null)
|
|
// {{{
|
|
{
|
|
$this->logMethods (__METHOD__, func_get_args ());
|
|
if ($val === null)
|
|
return $this->readMode;
|
|
if ($val !== "text" && $val !== "binary")
|
|
throw new \Exception ("Invalid readMode provided (nor text nor binary)",
|
|
500);
|
|
$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)
|
|
*/
|
|
final public function processName ($val = null)
|
|
// {{{
|
|
{
|
|
$this->logMethods (__METHOD__, func_get_args ());
|
|
if ($val === null)
|
|
return $this->processName;
|
|
$this->processName = $val;
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** 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)
|
|
* @param integer $port The port to listen
|
|
* @param callable $handler The handler that will be called when a client is
|
|
* connected to the address:port
|
|
*/
|
|
final public function init ($address, $port, $handler)
|
|
// {{{
|
|
{
|
|
$this->logMethods (__METHOD__, func_get_args ());
|
|
$this->nbChild = 0;
|
|
$this->handlers["$address:$port"] = $handler;
|
|
$this->addresses[] = $address;
|
|
$this->ports[] = $port;
|
|
return $this;
|
|
}
|
|
// }}}
|
|
|
|
/** Start the main loop after the init and keep in it until loopStop
|
|
*/
|
|
final public function loop ()
|
|
// {{{
|
|
{
|
|
$this->logMethods (__METHOD__, func_get_args ());
|
|
if (empty ($this->addresses))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Can not start TCP server loop without addresses initialized"), 500);
|
|
if (empty ($this->ports))
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Can not start TCP server loop without port initialized"), 500);
|
|
foreach ($this->ports as $port)
|
|
{
|
|
if ($port < 1024 && posix_getuid() !== 0)
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Can not start TCP server on port $port without root user"), 500);
|
|
}
|
|
$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"]);
|
|
cli_set_process_title ($this->processName." main");
|
|
foreach ($this->addresses as $key => $address)
|
|
{
|
|
$port = $this->ports[$key];
|
|
if (filter_var ($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))
|
|
$address = "$address";
|
|
elseif (filter_var ($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
|
|
$address = "[$address]";
|
|
else
|
|
throw new \Exception ("Can't create socket : invalid address provided");
|
|
$sockServer[$key] = stream_socket_server ("tcp://$address:$port",
|
|
$errno, $errstr);
|
|
if ($sockServer[$key] === false)
|
|
{
|
|
throw new \Exception ("Can't create socket : $errstr");
|
|
}
|
|
stream_set_timeout ($sockServer[$key], $this->timeout);
|
|
$this->logDebug ("Listening on $address:$port...");
|
|
}
|
|
$ppid = posix_getppid ();
|
|
while (1)
|
|
{
|
|
if (posix_getppid () !== $ppid)
|
|
{
|
|
$this->logDebug ("PARENT PID change : ".posix_getppid () .
|
|
" !== $ppid : STOP");
|
|
exit;
|
|
}
|
|
if ($this->loopStop)
|
|
{
|
|
$this->logDebug ("Do not accept new connections ($this->nbChild)");
|
|
foreach ($sockServer as $socket)
|
|
stream_socket_shutdown ($socket, STREAM_SHUT_RD);
|
|
if ($this->nbChild === 0)
|
|
{
|
|
$this->logDebug ("No more childs and loopStop requested".
|
|
" : end of process");
|
|
exit;
|
|
}
|
|
sleep (2);
|
|
continue;
|
|
}
|
|
$read = $sockServer;
|
|
$write = null;
|
|
$except = null;
|
|
// catch the errors, as a kill on a pending stream_select display a PHP
|
|
// Warning
|
|
if (@stream_select ($read, $write, $except, 0, 200000) < 1)
|
|
{
|
|
pcntl_signal_dispatch ();
|
|
continue;
|
|
}
|
|
foreach ($read as $sock)
|
|
{
|
|
$client = stream_socket_accept ($sock);
|
|
$name = stream_socket_get_name ($client, false);
|
|
$pos = strrpos ($name, ":");
|
|
$localPort = substr ($name, $pos+1);
|
|
$localAddress = substr ($name, 0, $pos);
|
|
$name = stream_socket_get_name ($client, true);
|
|
$pos = strrpos ($name, ":");
|
|
$port = substr ($name, $pos+1);
|
|
$address = substr ($name, 0, $pos);
|
|
$handlerAddress = $localAddress;
|
|
if (! key_exists ("$handlerAddress:$localPort", $this->handlers))
|
|
{
|
|
// If the address of the handler doesn't exists, the socket is maybe
|
|
// waiting on all the interfaces addresses
|
|
$handlerAddress = str_replace (["[", "]"], ["",""], $handlerAddress);
|
|
if (filter_var ($handlerAddress, FILTER_VALIDATE_IP,
|
|
FILTER_FLAG_IPV4))
|
|
$handlerAddress = "0.0.0.0";
|
|
elseif (filter_var ($handlerAddress, FILTER_VALIDATE_IP,
|
|
FILTER_FLAG_IPV6))
|
|
$handlerAddress = "::";
|
|
if (! key_exists ("$handlerAddress:$localPort", $this->handlers))
|
|
{
|
|
printf ("Can't find the handler for %s:%d\n", $handlerAddress,
|
|
$localPort);
|
|
continue;
|
|
}
|
|
}
|
|
if (substr ($address, 0, 8) === "[::ffff:")
|
|
$address = substr ($address, 8, -1);
|
|
if (substr ($localAddress, 0, 7) === "[::ffff:")
|
|
$localAddress = substr ($localAddress, 8, -1);
|
|
$this->logDebug ("New connection $address:$port > ".
|
|
"$localAddress:$localPort : ".
|
|
"use handler $handlerAddress:$localPort");
|
|
$handler = $this->handlers["$handlerAddress:$localPort"];
|
|
// We got a connection fork to manage it
|
|
if ($this->nbChild > $this->maxChild)
|
|
{
|
|
printf ("Too much child process %d : Abort the connection !!\n",
|
|
$this->nbChild);
|
|
@stream_socket_shutdown ($client, STREAM_SHUT_RDWR);
|
|
continue;
|
|
}
|
|
$pid = pcntl_fork();
|
|
if ($pid == -1)
|
|
{
|
|
throw new \Exception ("TCPServer can not fork", 500);
|
|
}
|
|
else if ($pid)
|
|
{
|
|
// parent process: return to the main loop
|
|
$this->nbChild++;
|
|
continue;
|
|
}
|
|
|
|
$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);
|
|
$sid = posix_setsid();
|
|
// In the child. Will call the handler with the actual tcpserver object
|
|
// as parameter
|
|
$this->parent = false;
|
|
$this->socket = $client;
|
|
if (is_array ($handler))
|
|
{
|
|
$object = $handler[0];
|
|
$method = $handler[1];
|
|
$object->$method ($this);
|
|
}
|
|
else
|
|
{
|
|
$function = $handler;
|
|
$function ($this);
|
|
}
|
|
$this->logDebug ("Child ended ($address:$port)");
|
|
exit;
|
|
}
|
|
}
|
|
}
|
|
// }}}
|
|
|
|
/** Request the loop to stop. Will not allow new connections, but wait the
|
|
* end of the existing processus
|
|
* Block until all is closed
|
|
*/
|
|
final public function loopStop ()
|
|
// {{{
|
|
{
|
|
$this->logMethods (__METHOD__, func_get_args ());
|
|
$this->loopStop = true;
|
|
}
|
|
// }}}
|
|
|
|
/** Start the main loop in background and do not wait its end
|
|
* @return the PID of the child
|
|
*/
|
|
final public function loopInBackgroundStart ()
|
|
// {{{
|
|
{
|
|
$this->logMethods (__METHOD__, func_get_args ());
|
|
$this->logDebug ("Start loopInBackground");
|
|
$pid = pcntl_fork();
|
|
$this->pidLoopInBackground = $pid;
|
|
if ($pid == -1)
|
|
{
|
|
throw new \Exception ("TCPServer can not fork in background", 500);
|
|
}
|
|
else if ($pid)
|
|
{
|
|
$this->logDebug ("loopInBackground : child = ".
|
|
$this->pidLoopInBackground);
|
|
// parent process: return to the main loop
|
|
return $pid;
|
|
}
|
|
$sid = posix_setsid();
|
|
// Will catch all the text messages from the application to not crash if
|
|
// there is an "echo"
|
|
ob_start ();
|
|
// Catch the error messages from the application to not hang if triggered
|
|
// An other handler can be set in function to execute
|
|
set_error_handler ([$this, "errorHandler"]);
|
|
set_exception_handler ([$this, "exceptionHandler"]);
|
|
@fclose (STDIN);
|
|
@fclose (STDOUT);
|
|
@fclose (STDERR);
|
|
$this->loop ();
|
|
exit;
|
|
}
|
|
// }}}
|
|
|
|
/** Stop the main loop in background and wait until its end
|
|
*/
|
|
final public function loopInBackgroundStop ()
|
|
// {{{
|
|
{
|
|
$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
|
|
*/
|
|
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)
|
|
throw new \Exception ("Can not send to client : not connected", 500);
|
|
return $this->socket;
|
|
}
|
|
// }}}
|
|
|
|
/** Get an array with the peer address, peer port, local address and local
|
|
* port
|
|
* @return array array ("peer address", peer port, "local address", local
|
|
* port)
|
|
*/
|
|
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)
|
|
throw new \Exception ("Can not send to client : not connected", 500);
|
|
$name = stream_socket_get_name ($this->socket, false);
|
|
$pos = strrpos ($name, ":");
|
|
$localPort = substr ($name, $pos+1);
|
|
$localAddress = substr ($name, 0, $pos);
|
|
$name = stream_socket_get_name ($this->socket, true);
|
|
$pos = strrpos ($name, ":");
|
|
$port = substr ($name, $pos+1);
|
|
$address = substr ($name, 0, $pos);
|
|
if (substr ($address, 0, 8) === "[::ffff:")
|
|
$address = substr ($address, 8, -1);
|
|
if (substr ($localAddress, 0, 7) === "[::ffff:")
|
|
$localAddress = substr ($localAddress, 8, -1);
|
|
return array ($address, $port, $localAddress, $localPort);
|
|
}
|
|
// }}}
|
|
|
|
/** Activate the SSL connection
|
|
* Put the socket in blocking mode, as it is mandatory to have SSL connection
|
|
* @param boolean $val True to activate, false to disable SSL
|
|
* @param integer $cryptoMethod The cryptoMethod allowed
|
|
*/
|
|
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);
|
|
// Setting the options allow the IP to be decided by the connect and valid
|
|
// the certificate of the server by the name
|
|
$options = array ("ssl" => array (
|
|
"verify_peer_name" => false,
|
|
));
|
|
stream_set_blocking ($this->socket, true);
|
|
stream_context_set_option ($this->socket, $options);
|
|
return @stream_socket_enable_crypto ($this->socket, !!$val, $cryptoMethod);
|
|
}
|
|
// }}}
|
|
|
|
/** Set context SSL option.
|
|
* @param array $options The ssl array to set
|
|
*/
|
|
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);
|
|
return stream_context_set_option ($this->socket, array ("ssl" => $options));
|
|
}
|
|
// }}}
|
|
|
|
/** Send data to the client
|
|
* @param mixed $data The data to send
|
|
* @return the length of data sent
|
|
*/
|
|
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);
|
|
if ($sentLen < $length)
|
|
throw new \Exception ("Can not send data to client", 500);
|
|
$this->logSend ($data);
|
|
return $sentLen;
|
|
}
|
|
// }}}
|
|
|
|
/** Read the data from the client.
|
|
* The connection must be established
|
|
* Use the readMode in text or binary (text by default)
|
|
* In text mode, the read return when found the first \r or the first \n.
|
|
* @param integer $maxLength Limit the length of the data from the server
|
|
* @return The content
|
|
*/
|
|
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)
|
|
throw new \Exception ("Can not read from client : not connected", 500);
|
|
stream_set_timeout ($this->socket, $this->timeout);
|
|
if ($this->readMode === "text")
|
|
$read = stream_get_line ($this->socket, $maxLength, "\r\n");
|
|
else
|
|
$read = @fread ($this->socket, $maxLength);
|
|
$meta = stream_get_meta_data ($this->socket);
|
|
if (isset ($meta["timed_out"]) && $meta["timed_out"] === true)
|
|
{
|
|
$this->timeoutHandler();
|
|
return false;
|
|
}
|
|
if ($read === false)
|
|
throw new \Exception ("Can not read from client : ".
|
|
error_get_last ()["message"], 500);
|
|
$this->logReceive ($read);
|
|
return $read;
|
|
}
|
|
// }}}
|
|
|
|
/** Disconnect the socket
|
|
*/
|
|
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)
|
|
throw new \Exception ("Can not disconnect client : not connected", 500);
|
|
@stream_socket_shutdown ($this->socket, STREAM_SHUT_RDWR);
|
|
$this->socket = null;
|
|
}
|
|
// }}}
|
|
|
|
/** 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)
|
|
// {{{
|
|
{
|
|
// @-operator : error suppressed
|
|
if (0 === error_reporting())
|
|
return false;
|
|
$this->logError ("line $line : $errMsg");
|
|
}
|
|
// }}}
|
|
|
|
/** Exception catcher
|
|
* By default do nothing, but can be overrided by the user
|
|
* @param object $exception The exception to catch
|
|
*/
|
|
public function exceptionHandler ($exception)
|
|
// {{{
|
|
{
|
|
$this->logError ("Exception ".$exception->getMessage () . " (".
|
|
$exception->getFile ().":".$exception->getLine().")");
|
|
}
|
|
// }}}
|
|
|
|
/** Manage the timeout handler
|
|
* By default, disconnect and generate an exception
|
|
*/
|
|
public function timeoutHandler ()
|
|
// {{{
|
|
{
|
|
$this->disconnect ();
|
|
throw new \Exception (dgettext ("domframework",
|
|
"Disconnected for inactivity"), 500);
|
|
}
|
|
// }}}
|
|
|
|
/////////////////////////
|
|
// PRIVATE METHODS //
|
|
/////////////////////////
|
|
/** Manage the child stop signal
|
|
*/
|
|
private function sigCHLD ()
|
|
// {{{
|
|
{
|
|
$this->nbChild --;
|
|
$this->logDebug ("One child finished : $this->nbChild childs remain ".
|
|
"active");
|
|
//pcntl_wait ($status, WNOHANG);
|
|
$rc = pcntl_wait ($status);
|
|
$this->logDebug ( "One child finished : $rc");
|
|
}
|
|
// }}}
|
|
|
|
/** Manage the term / int signals
|
|
* Will catch the stop signal, but the real end will be done when the last
|
|
* child will be closed
|
|
*/
|
|
private function sigTERMINT ()
|
|
// {{{
|
|
{
|
|
$this->logDebug ("Request TERM/INT : Wait for last childs");
|
|
$this->loopStop ();
|
|
}
|
|
// }}}
|
|
}
|