PHP 造玩具系列之实现简单的Proxy-Worker并发服务器

近来研究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);
}

 

贴上截图 ,由于测试具有波动性,本次测试只供参考

ee542ad0d69e1dcc520b748a43c1ada5ecf.jpg

转载于:https://my.oschina.net/u/4173863/blog/3100390

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值