近来研究php-fpm 对其并发比较感兴趣,遂造了一个tcp多进程并发服务器玩具,拿出来给大家看看(比较简单)
ps :不可直接复制运行 需要完成回调的逻辑
<?php
//设置worker进程数目
$workerNum=4;
//设置监听ip
$ip='0.0.0.0';
//设置监听端口
$port='9501';
//tcp连接池
$pool=[];
//事件池
$event_arr=[];
//进程池
$process=[];
//检查环境设置
if(PHP_VERSION<7) die('php版本至少7以上'.PHP_EOL);
//检查扩展
if(!extension_loaded('event')) die('未安装 evenet 扩展.'.PHP_EOL);
if(!extension_loaded('pcntl')) die('未安装 pcntl 扩展'.PHP_EOL);
// 只允许在cli下面运行
if (php_sapi_name()!="cli") die("只允许在cli下面运行".PHP_EOL);
/*
//守护进程
//由于进程组长无法创建会话,fork一个子进程并让父进程退出,以便可以创建新会话
switch(pcntl_fork())
{
case -1:
exit("fork error");
break;
case 0:
break;
default:
exit(0); //父进程退出
}
//创建新会话,脱离原来的控制终端
posix_setsid();
//再次fork并让父进程退出, 子进程不再是会话首进程,让其永远无法打开一个控制终端
switch(pcntl_fork()) {
case -1:
exit("fork error");
break;
case 0:
break;
default:
exit(0); //父进程退出
}
//关闭标准输入输出
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
fopen('/dev/null', 'r');
fopen('/dev/null', 'w');
fopen('/dev/null', 'w');
//切换工作目录
chdir('/');
//清除文件掩码
umask(0);
*/
//加载autoload设置
include('./vendor/autoload.php');
spl_autoload_register(['Autoload', 'load']);
//设置消息队列组
$queueGroup=[];
for($i=0;$i<$workerNum;$i++)
{
$queueGroup[$i]=msg_get_queue(ftok(__FILE__,$i));
}
//设置共享内存
$share_mem=shmop_open($share_key=ftok(__FILE__,'a'),'c',0644,4098);
//存入进程池
shmop_write($share_mem,serialize($process),0);
//设置进程名称
cli_set_process_title('tcpserver-master');
//安装信号异步处理
pcntl_async_signals(true);
//pcntl_signal_dispatch();
//启动worker进程
for ($i=0; $i < $workerNum; $i++)
{
//复制worker进程
$worker_pid=pcntl_fork();
if($worker_pid==0)
{
//参数
$is_close=false;
$is_restart=false;
//注册安全重启
pcntl_signal(SIGUSR1,function(){
global $is_restart;
$is_restart=true;
});
//注册关闭信号
pcntl_signal(SIGUSR2,function(){
global $is_close;
$is_close=true;
});
//设置进程名称
//cli_set_process_title('tcpserver-worker');
echo '子进程:'.getmypid().' 已启动!'.PHP_EOL;
while(true)
{
//接受消息队列
msg_receive($queueGroup[$i],1,$msgType,1024,$message);
//to do .. 伪代码
$queue_message=json_decode($message,true);
**********在这里可以实现自己的OnReceive业务逻辑***********
msg_send($queueGroup[$i],这里传入业务逻辑的结果);
//检测是否有信号传送
if($is_close)
{
echo '进程已关闭...'.PHP_EOL;
posix_kill(getmypid(),SIGKILL);
}
//热重启
if($is_restart)
{
$pid=pcntl_fork();
if($pid>0)
{
//移除进程池
$id=sem_get($share_key);
if(sem_acquire($id))
{
$arr=unserialize(shmop_read($share_mem,0,1024));
foreach($arr as $k=>$v)
{
if($v==getmypid())
{
unset($arr[$k]);
}
}
shmop_write($share_mem,serialize($arr),0);
sem_release($id);
}
//安全退出
posix_kill(getmypid(),SIGKILL);
}
if($pid==0)
{
//新进程加入进程池
$id=sem_get($share_key);
if(sem_acquire($id))
{
$arr=unserialize(shmop_read($share_mem,0,1024));
$arr['worker'][]=getmypid();
shmop_write($share_mem,serialize($arr),0);
sem_release($id);
}
echo '新进程:'.getmypid().' 已加入进程池'.PHP_EOL;
//恢复状态
$is_restart=false;
}
}
}
exit;
}
if($worker_pid>0)
{
global $share_key;
//记录子进程池
$id=sem_get($share_key);
if(sem_acquire($id))
{
$arr=unserialize(shmop_read($share_mem,0,1024));
$arr['worker'][]=$worker_pid;
shmop_write($share_mem,serialize($arr),0);
sem_release($id);
}
}
}
//启动proxy进程
$proxy_pid=pcntl_fork();
if($proxy_pid==0)
{
//设置进程名称
cli_set_process_title('tcpserver-proxy');
echo 'proxy进程id:'.getmypid().PHP_EOL;
//加载全局函数
global $pool,$event_arr;
//创建tcp服务器
$socket=socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
//绑定ip 、端口号
socket_bind($socket,$ip,$port);
//监听
socket_listen($socket);
//设置非阻塞模式
socket_set_nonblock($socket);
//设置事件
$event_base=new EventBase(new EventConfig());
//创建socket监听事件
$event=new Event($event_base,$socket,Event::READ | Event::PERSIST,function($socket){
//加载全局参数
global $event_arr,$pool,$event_base,$workerNum,$queueGroup,$index;
if(($conn=socket_accept($socket))!=false)
{
//设置连接非阻塞
socket_set_nonblock($conn);
//加入tcp连接池
$pool[(int)$conn]=$conn;
//消息打印
echo (int)$conn.' 已连接'.PHP_EOL;
***************在这里实现onConnect逻辑****************
//设置事件异步
$event_fd=new Event($event_base,$conn,Event::READ | Event::PERSIST,function($conn) use($workerNum,$queueGroup,$pool,$index){
global $event_arr;
//接受来自客户端的消息
$socketStatus=socket_recv($conn,$message,1024,0);
//echo $message;
if(!(bool)$socketStatus){
********************在这里实现onClose逻辑************************
echo (int)$conn.' 已离线!'.PHP_EOL;
//删除事件监听
$event_arr[(int)$conn]->free();
unset($event_arr[(int)$conn]);
//删除链接监听
unset($pool[(int)$conn]);
socket_close($conn);
}
if($socketStatus)
{
$return_arr=[
'k'=>(int)$conn,
'c'=>$message
];
msg_send($queueGroup[$return_arr['k']%$workerNum],1,json_encode($return_arr));
msg_receive($queueGroup[$return_arr['k']%$workerNum],$return_arr['k'],$msgType,1024,$rmessage);
socket_write($conn,$rmessage,strlen($rmessage));
}
},$conn);
//加入事件循环
$event_fd->add();
//加入事件池
$event_arr[(int)$conn]=$event_fd;
}
},$socket);
//加入事件循环
$event->add();
//加入事件连接池
$event_arr[(int)$socket]=$event;
//启动循环
$event_base->loop();
exit;
}
if($proxy_pid>0)
{
global $share_key;
//记录子进程池
$id=sem_get($share_key);
if(sem_acquire($id))
{
$arr=unserialize(shmop_read($share_mem,0,1024));
$arr['proxy']=$proxy_pid;
shmop_write($share_mem,serialize($arr),0);
sem_release($id);
}
}
//注册平滑重启信号
pcntl_signal(SIGUSR1,function(){
// to do...
echo '----正在执行安全重启----'.PHP_EOL;
global $share_key;
global $share_mem;
$id=sem_get($share_key);
if(sem_acquire($id))
{
$process=unserialize(shmop_read($share_mem,0,1024));
sem_release($id);
}
//发送重启信号
foreach ($process['worker'] as $v) {
posix_kill($v,SIGUSR1);
}
});
//注册平滑关闭信号
pcntl_signal(SIGUSR2,function(){
global $share_key;
global $share_mem;
$id=sem_get($share_key);
if(sem_acquire($id))
{
$process=unserialize(shmop_read($share_mem,0,1024));
sem_release($id);
}
echo '---正在执行安全关闭---'.PHP_EOL;
//发送关闭信号
foreach ($process['worker'] as $v) {
posix_kill($v,SIGUSR2);
}
});
//注册ctrl c信号
pcntl_signal(SIGINT,function(){
global $share_key;
global $share_mem;
global $queueGroup;
$id=sem_get($share_key);
if(sem_acquire($id))
{
$process=unserialize(shmop_read($share_mem,0,1024));
sem_release($id);
}
echo '---正在执行程序安全关闭---'.PHP_EOL;
//发送关闭信号
foreach ($process['worker'] as $v) {
posix_kill($v,SIGUSR2);
}
//关闭共享内存,消息队列
foreach ($queueGroup as $v) {
msg_remove_queue($v);
}
//关闭共享内存
shmop_close($share_mem);
shmop_delete($share_mem);
echo '------已关闭-----'.PHP_EOL;
posix_kill(SIGKILL,getmypid());
exit;
});
//主线程等待信号
echo 'master进程id:'.getmypid().PHP_EOL;
while(true)
{
sleep(1000);
}
做一下简单的性能测试,先给出测试代码
<?php
for($i=0;$i<10;$i++)
{
$pid=pcntl_fork();
if($pid==0)
{
$conn=stream_socket_client('tcp://0.0.0.0:9501');
$count=0;
pcntl_async_signals(true);
//pcntl_signal_dispatch();
pcntl_signal(SIGALRM,function(){
global $count;
echo getmypid().'---'.$count.PHP_EOL;
posix_kill(getmypid(),SIGKILL);
});
pcntl_alarm(1);
while(true)
{
stream_socket_sendto($conn,time());
stream_socket_recvfrom($conn,1024).PHP_EOL;
$count++;
}
}
}
while(true)
{
sleep(1);
}
贴上截图 ,由于测试具有波动性,本次测试只供参考