If you want to make a daemon process that forks on each client connection, you'll find out that there's a bug in PHP. The child processes send SIGCHLD to their parent when they exit but the parent can't handle signals while it's waiting for socket_accept (blocking).
Here is a piece of code that makes a non-blocking forking server.
#!/usr/bin/php -q
<?php
/**
* Listens for requests and forks on each connection
*/
$__server_listening = true;
error_reporting(E_ALL);
set_time_limit(0);
ob_implicit_flush();
declare(ticks = 1);
become_daemon();
/* nobody/nogroup, change to your host's uid/gid of the non-priv user */
change_identity(65534, 65534);
/* handle signals */
pcntl_signal(SIGTERM, 'sig_handler');
pcntl_signal(SIGINT, 'sig_handler');
pcntl_signal(SIGCHLD, 'sig_handler');
/* change this to your own host / port */
server_loop("127.0.0.1", 1234);
/**
* Change the identity to a non-priv user
*/
function change_identity( $uid, $gid )
{
if( !posix_setgid( $gid ) )
{
print "Unable to setgid to " . $gid . "!\n";
exit;
}
if( !posix_setuid( $uid ) )
{
print "Unable to setuid to " . $uid . "!\n";
exit;
}
}
/**
* Creates a server socket and listens for incoming client connections
* @param string $address The address to listen on
* @param int $port The port to listen on
*/
function server_loop($address, $port)
{
GLOBAL $__server_listening;
if(($sock = socket_create(AF_INET, SOCK_STREAM, 0)) < 0)
{
echo "failed to create socket: ".socket_strerror($sock)."\n";
exit();
}
if(($ret = socket_bind($sock, $address, $port)) < 0)
{
echo "failed to bind socket: ".socket_strerror($ret)."\n";
exit();
}
if( ( $ret = socket_listen( $sock, 0 ) ) < 0 )
{
echo "failed to listen to socket: ".socket_strerror($ret)."\n";
exit();
}
socket_set_nonblock($sock);
echo "waiting for clients to connect\n";
while ($__server_listening)
{
$connection = @socket_accept($sock);
if ($connection === false)
{
usleep(100);
}elseif ($connection > 0)
{
handle_client($sock, $connection);
}else
{
echo "error: ".socket_strerror($connection);
die;
}
}
}
/**
* Signal handler
*/
function sig_handler($sig)
{
switch($sig)
{
case SIGTERM:
case SIGINT:
exit();
break;
case SIGCHLD:
pcntl_waitpid(-1, $status);
break;
}
}
/**
* Handle a new client connection
*/
function handle_client($ssock, $csock)
{
GLOBAL $__server_listening;
$pid = pcntl_fork();
if ($pid == -1)
{
/* fork failed */
echo "fork failure!\n";
die;
}elseif ($pid == 0)
{
/* child process */
$__server_listening = false;
socket_close($ssock);
interact($csock);
socket_close($csock);
}else
{
socket_close($csock);
}
}
function interact($socket)
{
/* TALK TO YOUR CLIENT */
}
/**
* Become a daemon by forking and closing the parent
*/
function become_daemon()
{
$pid = pcntl_fork();
if ($pid == -1)
{
/* fork failed */
echo "fork failure!\n";
exit();
}elseif ($pid)
{
/* close the parent */
exit();
}else
{
/* child becomes our daemon */
posix_setsid();
chdir('/');
umask(0);
return posix_getpid();
}
}
?>
socket_accept
(PHP 4 >= 4.1.0, PHP 5)
socket_accept — Accetta una connessione su un socket
Descrizione
$socket
)Questa funzione è SPERIMENTALE. Ovvero, il comportamento di questa funzione, il nome di questa funzione, in definitiva tutto ciò che è documentato qui può cambiare nei futuri rilasci del PHP senza preavviso. Siete avvisati, l'uso di questa funzione è a vostro rischio.
Dopo la creazione del socket socket con
socket_create(), l'assegnazione di un nome con
socket_bind(), e averlo messo in attesa di connessione
con socket_listen(), con questa funzione si inizia ad accettare
le richieste di connessione su quel socket. Una volta avuta una
connessione, la funzione restituisce un nuovo socket che può essere
usato per la comunicazione. Se vi sono diverse richieste di connessioni pendenti
verrà utilizzata la prima. Viceversa se non vi sono richieste in attesa,
la funzione socket_accept() si blocca in
attesa di una richiesta. Se il socket è stato
configurato "non-blocking" con
socket_set_blocking() o con
socket_set_nonblock(), la funzione restituirà FALSE.
La risorsa socket restituita da
socket_accept() non può essere utilizzata per acquisire
nuove connesioni. Per questo scopo occorre
continuare ad usare il socket originale, indicato in socket,
che rimane aperto.
La funzione restistuisce una risorsa di tipo socket se ha successo, oppure FALSE se si verifica un
errore. Il codice di errore può essere recuperato chiamando
la funzione socket_last_error(). Questo codice può
essere passato a socket_strerror() per ottenere una descrizione
dell'errore.
Vedere anche socket_bind(), socket_connect(), socket_listen(), socket_create() e socket_strerror().
If you want to have multiple clients on a server you will have to use non blocking.
<?php
$clients = array();
$socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
socket_bind($socket,'127.0.0.1',$port);
socket_listen($socket);
socket_set_nonblock($socket);
while(true)
{
if(($newc = socket_accept($socket)) !== false)
{
echo "Client $newc has connected\n";
$clients[] = $newc;
}
}
?>
If you want to know the remote address ( ip address ) and the port of an incoming connection that has been accepted with socket_accept(), you can use socket_getpeername()
Be aware signal handler functions set with pcntl_signal are not called while a socket is blocking waiting for a connection; the signal is absorbed silently and the handler called when a connection is made.
The socket returned by this resource will be non-blocking, regardless of what the listening socket is set to. This is actually true for all FCNTL modifiers.
Accepting a connection using php-sockets:
$fd = socket_create(AF_INET, SOCK_STREAM, 6 /* OR getprotobyname("TCP")*/);
$PORT = 5000;
socket_bind($fd, "0.0.0.0", $PORT);
while(true)
{
$remote_fd = socket_accept($fd);
remote_socket_client_handle($remote_fd);
}
It is simple!
socket_accept with timeout, seems to work for me on Apache/1.3.37 (FreeBSD 6.0) PHP/4.4.7.
Adapted from ScriptBlue at nyc dot rr dot com's post under socket_connect.
<?php
$socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
socket_bind($socket,$address,$port);
socket_listen($socket);
echo "Waiting for a connection\n";
$conn = false;
switch(@socket_select($r = array($socket), $w = array($socket), $e = array($socket), 60)) {
case 2:
echo "Connection refused\n";
break;
case 1:
echo "Connection accepted\n";
$conn = @socket_accept($socket);
break;
case 0:
echo "Connection timed out\n";
break;
}
if ($conn !== false) {
// communicate over $conn
}
?>
>Accepting a connection using php-sockets:
>
>$fd = socket_create(AF_INET, SOCK_STREAM, 6 /* OR >getprotobyname("TCP")*/);
>
>$PORT = 5000;
>
>socket_bind($fd, "0.0.0.0", $PORT);
>
>while(true)
>{
>$remote_fd = socket_accept($fd);
>
>remote_socket_client_handle($remote_fd);
>
>}
>
>It is simple!
This example doesn't work. You have to call socket_listen($fd) after your bind in order to accept incoming connections.
Simon
