PHP 多进程系列笔记(三)

606 阅读3分钟
原文链接: mp.weixin.qq.com

本节讲解几个多进程的实例。

多进程实例

Master-Worker结构

下面例子实现了简单的多进程管理:

  • 支持设置最大子进程数

  • Master-Worker结构:Worker挂掉,Master进程会重新创建一个

  1. <?php

  2. $pids = []; //存储子进程pid

  3. $MAX_PROCESS = 3;//最大进程数

  4. $pid = pcntl_fork();

  5. if($pid <0){

  6.    exit("fork fail\n");

  7. }elseif($pid > 0){

  8.    exit;//父进程退出

  9. }else{

  10.    // 从当前终端分离

  11.    if (posix_setsid() == -1) {

  12.        die("could not detach from terminal");

  13.    }

  14.    $id = getmypid();  

  15.    echo time()." Master process, pid {$id}\n";

  16.    for($i=0; $i<$MAX_PROCESS;$i++){

  17.        start_worker_process();

  18.    }

  19.    //Master进程等待子进程退出,必须是死循环

  20.    while(1){

  21.        foreach($pids as $pid){

  22.            if($pid){

  23.                $res = pcntl_waitpid($pid, $status, WNOHANG);

  24.                if ( $res == -1 || $res > 0 ){

  25.                    echo time()." Worker process $pid exit, will start new... \n";

  26.                    start_worker_process();

  27.                    unset($pids[$pid]);

  28.                }

  29.            }

  30.        }

  31.    }

  32. }

  33. /**

  34. * 创建worker进程

  35. */

  36. function start_worker_process(){

  37.    global $pids;

  38.    $pid = pcntl_fork();

  39.    if($pid <0){

  40.        exit("fork fail\n");

  41.    }elseif($pid > 0){

  42.        $pids[$pid] = $pid;

  43.        // exit; //此处不可退出,否则Master进程就退出了

  44.    }else{

  45.        //实际代码

  46.        $id = getmypid();  

  47.        $rand = rand(1,3);

  48.        echo time()." Worker process, pid {$id}. run $rand s\n";

  49.        while(1){

  50.            sleep($rand);

  51.        }

  52.    }

  53. }


防盗版声明:本文系原创文章,发布于公众号 飞鸿影的博客(fhyblog)及博客园,转载需作者同意。


多进程Server

下面我们使用多进程实现一个tcp服务器,支持:

  • 多进程处理客户端连接

  • 子进程退出,Master进程会重新创建一个

  • 支持事件回调

  1. <?php

  2. class TcpServer{

  3.    const MAX_PROCESS = 3;//最大进程数

  4.    private $pids = []; //存储子进程pid

  5.    private $socket;

  6.    public function __construct(){

  7.        $pid = pcntl_fork();

  8.        if($pid <0){

  9.            exit("fork fail\n");

  10.        }elseif($pid > 0){

  11.            exit;//父进程退出

  12.        } else{

  13.            // 从当前终端分离

  14.            if (posix_setsid() == -1) {

  15.                die("could not detach from terminal");

  16.            }

  17.            umask(0);

  18.            $id = getmypid();  

  19.            echo time()." Master process, pid {$id}\n";

  20.            //创建tcp server

  21.            $this->socket = stream_socket_server("tcp://0.0.0.0:9201", $errno, $errstr);

  22.            if(!$this->socket) exit("start server err: $errstr --- $errno");

  23.        }

  24.    }

  25.    public function run(){

  26.        for($i=0; $i<self::MAX_PROCESS;$i++){

  27.            $this->start_worker_process();

  28.        }

  29.        echo "waiting client...\n";

  30.        //Master进程等待子进程退出,必须是死循环

  31.        while(1){

  32.            foreach($this->pids as $k=>$pid){

  33.                if($pid){

  34.                    $res = pcntl_waitpid($pid, $status, WNOHANG);

  35.                    if ( $res == -1 || $res > 0 ){

  36.                        echo time()." Worker process $pid exit, will start new... \n";

  37.                        $this->start_worker_process();

  38.                        unset($this->pids[$k]);

  39.                    }

  40.                }

  41.            }

  42.            sleep(1);//让出1s时间给CPU

  43.        }

  44.    }

  45.    /**

  46.     * 创建worker进程,接受客户端连接

  47.     */

  48.    private function start_worker_process(){

  49.        $pid = pcntl_fork();

  50.        if($pid <0){

  51.            exit("fork fail\n");

  52.        }elseif($pid > 0){

  53.            $this->pids[] = $pid;

  54.            // exit; //此处不可退出,否则Master进程就退出了

  55.        }else{

  56.            $this->acceptClient();

  57.        }

  58.    }

  59.    private function acceptClient()

  60.    {

  61.        //子进程一直等待客户端连接,不能退出

  62.        while(1){

  63.            $conn = stream_socket_accept($this->socket, -1);

  64.            if($this->onConnect) call_user_func($this->onConnect, $conn); //回调连接事件

  65.            //开始循环读取消息

  66.            $recv = ''; //实际收到消息

  67.            $buffer = ''; //缓冲消息

  68.            while(1){

  69.                $buffer = fread($conn, 20);

  70.                //没有收到正常消息

  71.                if($buffer === false || $buffer === ''){

  72.                    if($this->onClose) call_user_func($this->onClose, $conn); //回调断开连接事件

  73.                    break;//结束读取消息,等待下一个客户端连接

  74.                }

  75.                $pos = strpos($buffer, "\n"); //消息结束符

  76.                if($pos === false){

  77.                    $recv .= $buffer;                            

  78.                }else{

  79.                    $recv .= trim(substr($buffer, 0, $pos+1));

  80.                    if($this->onMessage) call_user_func($this->onMessage, $conn, $recv); //回调收到消息事件

  81.                    //客户端强制关闭连接

  82.                    if($recv == "quit"){

  83.                        echo "client close conn\n";

  84.                        fclose($conn);

  85.                        break;

  86.                    }

  87.                    $recv = ''; //清空消息,准备下一次接收

  88.                }

  89.            }

  90.        }

  91.    }

  92.    function __destruct() {

  93.        @fclose($this->socket);

  94.    }

  95. }

  96. $server =  new TcpServer();

  97. $server->onConnect = function($conn){

  98.    echo "onConnect -- accepted " . stream_socket_get_name($conn,true) . "\n";

  99.    fwrite($conn,"conn success\n");

  100. };

  101. $server->onMessage = function($conn,$msg){

  102.    echo "onMessage --" . $msg . "\n";

  103.    fwrite($conn,"received ".$msg."\n");

  104. };

  105. $server->onClose = function($conn){

  106.    echo "onClose --" . stream_socket_get_name($conn,true) . "\n";

  107.    fwrite($conn,"onClose "."\n");

  108. };

  109. $server->run();

运行:

  1. $ php process_multi.server.php

  2. 1528734803 Master process, pid 9110

  3. waiting client...

此时服务端已经变成守护进程了。新开终端,我们使用ps命令查看进程:

  1. $ ps -ef | grep php

  2. yjc       9110     1  0 00:33 ?        00:00:00 php process_multi.server.php

  3. yjc       9111  9110  0 00:33 ?        00:00:00 php process_multi.server.php

  4. yjc       9112  9110  0 00:33 ?        00:00:00 php process_multi.server.php

  5. yjc       9113  9110  0 00:33 ?        00:00:00 php process_multi.server.php

  6. yjc       9134  8589  0 00:35 pts/1    00:00:00 grep php

可以看到4个进程:1个主进程,3个子进程。使用kill命令结束子进程,主进程会重新拉起一个新的子进程。

然后我们使用telnet测试连接:

  1. $ telnet 127.0.0.1 9201

  2. Trying 127.0.0.1...

  3. Connected to 127.0.0.1.

  4. Escape character is '^]'.

  5. conn success

  6. hello server!

  7. received hello server!

  8. quit

  9. received quit

  10. Connection closed by foreign host.