如果您想创建一个在每次客户端连接时都进行 fork 的守护进程,您会发现 PHP 中存在一个错误。子进程在退出时会向其父进程发送 SIGCHLD,但父进程在等待 socket_accept(阻塞)时无法处理信号。
以下代码段创建了一个非阻塞 fork 服务器。
#!/usr/bin/php -q
<?php
$__server_listening = true;
error_reporting(E_ALL);
set_time_limit(0);
ob_implicit_flush();
declare(ticks = 1);
become_daemon();
change_identity(65534, 65534);
pcntl_signal(SIGTERM, 'sig_handler');
pcntl_signal(SIGINT, 'sig_handler');
pcntl_signal(SIGCHLD, 'sig_handler');
server_loop("127.0.0.1", 1234);
function change_identity( $uid, $gid )
{
if( !posix_setgid( $gid ) )
{
print "无法将 gid 设置为 " . $gid . "!\n";
exit;
}
if( !posix_setuid( $uid ) )
{
print "无法将 uid 设置为 " . $uid . "!\n";
exit;
}
}
function server_loop($address, $port)
{
GLOBAL $__server_listening;
if(($sock = socket_create(AF_INET, SOCK_STREAM, 0)) < 0)
{
echo "创建套接字失败: ".socket_strerror($sock)."\n";
exit();
}
if(($ret = socket_bind($sock, $address, $port)) < 0)
{
echo "绑定套接字失败: ".socket_strerror($ret)."\n";
exit();
}
if( ( $ret = socket_listen( $sock, 0 ) ) < 0 )
{
echo "监听套接字失败: ".socket_strerror($ret)."\n";
exit();
}
socket_set_nonblock($sock);
echo "等待客户端连接\n";
while ($__server_listening)
{
$connection = @socket_accept($sock);
if ($connection === false)
{
usleep(100);
}elseif ($connection > 0)
{
handle_client($sock, $connection);
}else
{
echo "错误: ".socket_strerror($connection);
die;
}
}
}
function sig_handler($sig)
{
switch($sig)
{
case SIGTERM:
case SIGINT:
exit();
break;
case SIGCHLD:
pcntl_waitpid(-1, $status);
break;
}
}
function handle_client($ssock, $csock)
{
GLOBAL $__server_listening;
$pid = pcntl_fork();
if ($pid == -1)
{
echo "fork 失败!\n";
die;
}elseif ($pid == 0)
{
$__server_listening = false;
socket_close($ssock);
interact($csock);
socket_close($csock);
}else
{
socket_close($csock);
}
}
function interact($socket)
{
}
function become_daemon()
{
$pid = pcntl_fork();
if ($pid == -1)
{
echo "fork 失败!\n";
exit();
}elseif ($pid)
{
exit();
}else
{
posix_setsid();
chdir('/');
umask(0);
return posix_getpid();
}
}
?>