tcpserver : Child management : catch the TERM/INT/CHLD signals
git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@5973 bf3deb0d-5f1a-0410-827f-c0cc1f45334c
This commit is contained in:
188
tcpserver.php
188
tcpserver.php
@@ -18,6 +18,13 @@
|
|||||||
*/
|
*/
|
||||||
class tcpserver
|
class tcpserver
|
||||||
{
|
{
|
||||||
|
////////////////////
|
||||||
|
// PROPERTIES //
|
||||||
|
////////////////////
|
||||||
|
// {{{
|
||||||
|
/** Allow to debug with message on screen
|
||||||
|
*/
|
||||||
|
private $debug = true;
|
||||||
/** Store the data concerning the sockets and the handlers
|
/** Store the data concerning the sockets and the handlers
|
||||||
*/
|
*/
|
||||||
private $handlers = array ();
|
private $handlers = array ();
|
||||||
@@ -43,22 +50,35 @@ class tcpserver
|
|||||||
/** Read in Text mode or in Binary mode
|
/** Read in Text mode or in Binary mode
|
||||||
*/
|
*/
|
||||||
private $readMode = "text";
|
private $readMode = "text";
|
||||||
|
/** Stop the new connections
|
||||||
|
*/
|
||||||
|
private $loopStop = false;
|
||||||
|
/** The number of active clients
|
||||||
|
*/
|
||||||
|
private $nbChild = 0;
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
////////////////////////
|
||||||
|
// PUBLIC METHODS //
|
||||||
|
////////////////////////
|
||||||
/** 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)
|
public function maxChild ($val = null)
|
||||||
|
// {{{
|
||||||
{
|
{
|
||||||
if ($val === null)
|
if ($val === null)
|
||||||
return $this->maxChild;
|
return $this->maxChild;
|
||||||
$this->maxChild = intval ($val);
|
$this->maxChild = intval ($val);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
/** 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 $readMode The mode to set (or get if null)
|
||||||
*/
|
*/
|
||||||
public function readMode ($readMode = null)
|
public function readMode ($readMode = null)
|
||||||
|
// {{{
|
||||||
{
|
{
|
||||||
if ($readMode === null)
|
if ($readMode === null)
|
||||||
return $this->readMode;
|
return $this->readMode;
|
||||||
@@ -68,6 +88,7 @@ class tcpserver
|
|||||||
$this->readMode = $readMode;
|
$this->readMode = $readMode;
|
||||||
return $this;
|
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
|
||||||
@@ -77,40 +98,64 @@ class tcpserver
|
|||||||
* connected to the address:port
|
* connected to the address:port
|
||||||
*/
|
*/
|
||||||
public function init ($address, $port, $handler)
|
public function init ($address, $port, $handler)
|
||||||
|
// {{{
|
||||||
{
|
{
|
||||||
|
$this->nbChild = 0;
|
||||||
$this->handlers["$address:$port"] = $handler;
|
$this->handlers["$address:$port"] = $handler;
|
||||||
$this->addresses[] = $address;
|
$this->addresses[] = $address;
|
||||||
$this->ports[] = $port;
|
$this->ports[] = $port;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
/** Start the main loop after the init
|
/** Start the main loop after the init and keep in it until loopStop
|
||||||
*/
|
*/
|
||||||
public function loop ()
|
public function loop ()
|
||||||
|
// {{{
|
||||||
{
|
{
|
||||||
|
declare(ticks = 10);
|
||||||
|
pcntl_async_signals (true);
|
||||||
|
pcntl_signal (SIGCHLD,[$this, "sigCHLD"]);
|
||||||
|
pcntl_signal (SIGTERM,[$this, "sigTERMINT"]);
|
||||||
|
pcntl_signal (SIGINT,[$this, "sigTERMINT"]);
|
||||||
foreach ($this->addresses as $key => $address)
|
foreach ($this->addresses as $key => $address)
|
||||||
{
|
{
|
||||||
$port = $this->ports[$key];
|
$port = $this->ports[$key];
|
||||||
if (filter_var ($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))
|
if (filter_var ($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))
|
||||||
$sockServer[$key] = stream_socket_server ("tcp://$address:$port",
|
$address = "$address";
|
||||||
$errno, $errstr);
|
|
||||||
elseif (filter_var ($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
|
elseif (filter_var ($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
|
||||||
$sockServer[$key] = stream_socket_server ("tcp://[$address]:$port",
|
$address = "[$address]";
|
||||||
$errno, $errstr);
|
|
||||||
else
|
else
|
||||||
throw new \Exception ("Can't create socket : invalid address provided");
|
throw new \Exception ("Can't create socket : invalid address provided");
|
||||||
|
$sockServer[$key] = stream_socket_server ("tcp://$address:$port",
|
||||||
|
$errno, $errstr);
|
||||||
if ($sockServer[$key] === false)
|
if ($sockServer[$key] === false)
|
||||||
{
|
{
|
||||||
throw new \Exception ("Can't create socket : $errstr");
|
throw new \Exception ("Can't create socket : $errstr");
|
||||||
}
|
}
|
||||||
//printf ("Listening on %s:%d...\n", $address, $port);
|
if ($this->debug)
|
||||||
|
printf ("[".posix_getpid ()."] Listening on %s:%d...\n",
|
||||||
|
$address, $port);
|
||||||
}
|
}
|
||||||
$nbChild = 1;
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
// Watch if a child process is zombie, then destroy it
|
if ($this->loopStop)
|
||||||
if (pcntl_wait ($status, WNOHANG) > 0)
|
{
|
||||||
$nbChild--;
|
if ($this->debug)
|
||||||
|
echo "[".posix_getpid ()."] Do not accept new connections ".
|
||||||
|
"($this->nbChild)\n";
|
||||||
|
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";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
sleep (2);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$read = $sockServer;
|
$read = $sockServer;
|
||||||
$write = null;
|
$write = null;
|
||||||
$except = null;
|
$except = null;
|
||||||
@@ -137,24 +182,34 @@ class tcpserver
|
|||||||
{
|
{
|
||||||
// If the address of the handler doesn't exists, the socket is maybe
|
// If the address of the handler doesn't exists, the socket is maybe
|
||||||
// waiting on all the interfaces addresses
|
// waiting on all the interfaces addresses
|
||||||
if (filter_var ($localAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))
|
$handlerAddress = str_replace (["[", "]"], ["",""], $handlerAddress);
|
||||||
|
if (filter_var ($handlerAddress, FILTER_VALIDATE_IP,
|
||||||
|
FILTER_FLAG_IPV4))
|
||||||
$handlerAddress = "0.0.0.0";
|
$handlerAddress = "0.0.0.0";
|
||||||
elseif (filter_var ($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
|
elseif (filter_var ($handlerAddress, FILTER_VALIDATE_IP,
|
||||||
|
FILTER_FLAG_IPV6))
|
||||||
$handlerAddress = "::";
|
$handlerAddress = "::";
|
||||||
if (! key_exists ("$handlerAddress:$localPort", $this->handlers))
|
if (! key_exists ("$handlerAddress:$localPort", $this->handlers))
|
||||||
{
|
{
|
||||||
printf ("Can't find the handler for %s:%d\n", $localAddress,
|
printf ("Can't find the handler for %s:%d\n", $handlerAddress,
|
||||||
$localPort);
|
$localPort);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//echo "Enter $address:$port > $localAddress:$localPort\n";
|
if (substr ($address, 0, 8) === "[::ffff:")
|
||||||
|
$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 > ".
|
||||||
|
"$localAddress:$localPort : ".
|
||||||
|
"use handler $handlerAddress:$localPort\n";
|
||||||
$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 ($nbChild > $this->maxChild)
|
if ($this->nbChild > $this->maxChild)
|
||||||
{
|
{
|
||||||
printf ("Too much child process %d : Abort the connection !!\n",
|
printf ("Too much child process %d : Abort the connection !!\n",
|
||||||
$nbChild);
|
$this->nbChild);
|
||||||
@stream_socket_shutdown ($client, STREAM_SHUT_RDWR);
|
@stream_socket_shutdown ($client, STREAM_SHUT_RDWR);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -166,10 +221,16 @@ class tcpserver
|
|||||||
else if ($pid)
|
else if ($pid)
|
||||||
{
|
{
|
||||||
// parent process: return to the main loop
|
// parent process: return to the main loop
|
||||||
$nbChild++;
|
$this->nbChild++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->debug)
|
||||||
|
echo "[".posix_getpid ()."] Child start ($address:$port) : ".
|
||||||
|
($this->nbChild+1)." child active\n";
|
||||||
|
// Do not stop if the parent is requesting a stop from Ctrl+C
|
||||||
|
pcntl_signal(SIGINT, SIG_IGN);
|
||||||
|
$sid = posix_setsid();
|
||||||
// In the child. Will call the handler with the actual tcpserver object
|
// In the child. Will call the handler with the actual tcpserver object
|
||||||
// as parameter
|
// as parameter
|
||||||
$this->parent = false;
|
$this->parent = false;
|
||||||
@@ -185,15 +246,60 @@ class tcpserver
|
|||||||
$function = $handler;
|
$function = $handler;
|
||||||
$function ($this);
|
$function ($this);
|
||||||
}
|
}
|
||||||
exit;
|
if ($this->debug)
|
||||||
|
echo "[".posix_getpid ()."] Child ended ($address:$port)\n";
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/** Request the loop to stop. Will not allow new connections, but wait the
|
||||||
|
* end of the existing processus
|
||||||
|
* Block until all is closed
|
||||||
|
*/
|
||||||
|
public function loopStop ()
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$this->loopStop = true;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/** Start the main loop in background and do not wait its end
|
||||||
|
* @return the PID of the child
|
||||||
|
*/
|
||||||
|
public function loopInBackground ()
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$pid = pcntl_fork();
|
||||||
|
if ($pid == -1)
|
||||||
|
{
|
||||||
|
throw new \Exception ("TCPServer can not fork in background", 500);
|
||||||
|
}
|
||||||
|
else if ($pid)
|
||||||
|
{
|
||||||
|
// parent process: return to the main loop
|
||||||
|
return $pid;
|
||||||
|
}
|
||||||
|
echo "CHILD";
|
||||||
|
$sid = posix_setsid();
|
||||||
|
// Will catch all the text messages from the application to not crash if
|
||||||
|
// there is an "echo"
|
||||||
|
ob_start ();
|
||||||
|
@fclose (STDIN);
|
||||||
|
@fclose (STDOUT);
|
||||||
|
@fclose (STDERR);
|
||||||
|
echo "IN";
|
||||||
|
$this->loop ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
/** 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 ()
|
public function getSock ()
|
||||||
|
// {{{
|
||||||
{
|
{
|
||||||
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);
|
||||||
@@ -201,6 +307,7 @@ class tcpserver
|
|||||||
throw new \Exception ("Can not send to client : not connected", 500);
|
throw new \Exception ("Can not send to client : not connected", 500);
|
||||||
return $this->socket;
|
return $this->socket;
|
||||||
}
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
/** Get an array with the peer address, peer port, local address and local
|
/** Get an array with the peer address, peer port, local address and local
|
||||||
* port
|
* port
|
||||||
@@ -208,6 +315,7 @@ class tcpserver
|
|||||||
* port)
|
* port)
|
||||||
*/
|
*/
|
||||||
public function getInfo ()
|
public function getInfo ()
|
||||||
|
// {{{
|
||||||
{
|
{
|
||||||
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);
|
||||||
@@ -221,8 +329,13 @@ class tcpserver
|
|||||||
$pos = strrpos ($name, ":");
|
$pos = strrpos ($name, ":");
|
||||||
$port = substr ($name, $pos+1);
|
$port = substr ($name, $pos+1);
|
||||||
$address = substr ($name, 0, $pos);
|
$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);
|
return array ($address, $port, $localAddress, $localPort);
|
||||||
}
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
/** Activate the SSL connection
|
/** Activate the SSL connection
|
||||||
* Put the socket in blocking mode, as it is mandatory to have SSL connection
|
* Put the socket in blocking mode, as it is mandatory to have SSL connection
|
||||||
@@ -231,6 +344,7 @@ class tcpserver
|
|||||||
*/
|
*/
|
||||||
public function cryptoEnable ($val,
|
public function cryptoEnable ($val,
|
||||||
$cryptoMethod = STREAM_CRYPTO_METHOD_TLS_SERVER)
|
$cryptoMethod = STREAM_CRYPTO_METHOD_TLS_SERVER)
|
||||||
|
// {{{
|
||||||
{
|
{
|
||||||
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 : ".
|
||||||
@@ -244,23 +358,27 @@ class tcpserver
|
|||||||
stream_context_set_option ($this->socket, $options);
|
stream_context_set_option ($this->socket, $options);
|
||||||
return @stream_socket_enable_crypto ($this->socket, !!$val, $cryptoMethod);
|
return @stream_socket_enable_crypto ($this->socket, !!$val, $cryptoMethod);
|
||||||
}
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
/** 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)
|
public function setSSLOptions ($options)
|
||||||
|
// {{{
|
||||||
{
|
{
|
||||||
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);
|
||||||
return stream_context_set_option ($this->socket, array ("ssl" => $options));
|
return stream_context_set_option ($this->socket, array ("ssl" => $options));
|
||||||
}
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
/** Send data to the client
|
/** Send data to the client
|
||||||
* @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)
|
public function send ($data)
|
||||||
|
// {{{
|
||||||
{
|
{
|
||||||
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);
|
||||||
@@ -272,6 +390,7 @@ class tcpserver
|
|||||||
throw new \Exception ("Can not send data to client", 500);
|
throw new \Exception ("Can not send data to client", 500);
|
||||||
return $sentLen;
|
return $sentLen;
|
||||||
}
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
/** Read the data from the client.
|
/** Read the data from the client.
|
||||||
* The connection must be established
|
* The connection must be established
|
||||||
@@ -281,6 +400,7 @@ class tcpserver
|
|||||||
* @return The content
|
* @return The content
|
||||||
*/
|
*/
|
||||||
public function read ($maxLength = 1024)
|
public function read ($maxLength = 1024)
|
||||||
|
// {{{
|
||||||
{
|
{
|
||||||
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);
|
||||||
@@ -302,10 +422,12 @@ class tcpserver
|
|||||||
}
|
}
|
||||||
return $read;
|
return $read;
|
||||||
}
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
/** Disconnect the socket
|
/** Disconnect the socket
|
||||||
*/
|
*/
|
||||||
public function disconnect ()
|
public function disconnect ()
|
||||||
|
// {{{
|
||||||
{
|
{
|
||||||
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);
|
||||||
@@ -314,4 +436,34 @@ class tcpserver
|
|||||||
@stream_socket_shutdown ($this->socket, STREAM_SHUT_RDWR);
|
@stream_socket_shutdown ($this->socket, STREAM_SHUT_RDWR);
|
||||||
$this->socket = null;
|
$this->socket = null;
|
||||||
}
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// PRIVATE METHODS //
|
||||||
|
/////////////////////////
|
||||||
|
/** Manage the child stop signal
|
||||||
|
*/
|
||||||
|
private function sigCHLD ()
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$this->nbChild --;
|
||||||
|
if ($this->debug)
|
||||||
|
echo "[".posix_getpid ()."] One child finished : $this->nbChild childs ".
|
||||||
|
"remain active\n";
|
||||||
|
pcntl_wait ($status, WNOHANG);
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/** 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 ()
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
if ($this->debug)
|
||||||
|
echo "[".posix_getpid ()."] Request TERM/INT : Wait for last childs\n";
|
||||||
|
$this->loopStop ();
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user