• PHP多进程系列笔记(三)


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

    多进程实例

    Master-Worker结构

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

    • 支持设置最大子进程数
    • Master-Worker结构:Worker挂掉,Master进程会重新创建一个
    <?php 
    
    $pids = []; //存储子进程pid
    $MAX_PROCESS = 3;//最大进程数
    
    $pid = pcntl_fork();
    if($pid <0){
        exit("fork fail
    ");
    }elseif($pid > 0){
        exit;//父进程退出
    }else{
        // 从当前终端分离
        if (posix_setsid() == -1) {
            die("could not detach from terminal");
        }
    
        $id = getmypid();   
        echo time()." Master process, pid {$id}
    "; 
    
        for($i=0; $i<$MAX_PROCESS;$i++){
            start_worker_process();
        }
    
        //Master进程等待子进程退出,必须是死循环
        while(1){
            foreach($pids as $pid){
                if($pid){
                    $res = pcntl_waitpid($pid, $status, WNOHANG);
                    if ( $res == -1 || $res > 0 ){
                        echo time()." Worker process $pid exit, will start new... 
    ";
                        start_worker_process();
                        unset($pids[$pid]);
                    }
                }
            }
        }
    }
    
    /**
     * 创建worker进程
     */
    function start_worker_process(){
        global $pids;
        $pid = pcntl_fork();
        if($pid <0){
            exit("fork fail
    ");
        }elseif($pid > 0){
            $pids[$pid] = $pid;
            // exit; //此处不可退出,否则Master进程就退出了
        }else{
            //实际代码
            $id = getmypid();   
            $rand = rand(1,3);
            echo time()." Worker process, pid {$id}. run $rand s
    "; 
            while(1){
                sleep($rand);
            }
        }
    }
    

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

    多进程Server

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

    • 多进程处理客户端连接
    • 子进程退出,Master进程会重新创建一个
    • 支持事件回调
    <?php 
    
    class TcpServer{
        const MAX_PROCESS = 3;//最大进程数
        private $pids = []; //存储子进程pid
        private $socket;
    
        public function __construct(){
            $pid = pcntl_fork();
            if($pid <0){
                exit("fork fail
    ");
            }elseif($pid > 0){
                exit;//父进程退出
            } else{
                // 从当前终端分离
                if (posix_setsid() == -1) {
                    die("could not detach from terminal");
                }
    
                umask(0);
    
                $id = getmypid();   
                echo time()." Master process, pid {$id}
    "; 
    
                //创建tcp server
                $this->socket = stream_socket_server("tcp://0.0.0.0:9201", $errno, $errstr);
                if(!$this->socket) exit("start server err: $errstr --- $errno");
            }
        }
    
        public function run(){
            for($i=0; $i<self::MAX_PROCESS;$i++){
                $this->start_worker_process();
            }
    
            echo "waiting client...
    ";
    
            //Master进程等待子进程退出,必须是死循环
            while(1){
                foreach($this->pids as $k=>$pid){
                    if($pid){
                        $res = pcntl_waitpid($pid, $status, WNOHANG);
                        if ( $res == -1 || $res > 0 ){
                            echo time()." Worker process $pid exit, will start new... 
    ";
                            $this->start_worker_process();
                            unset($this->pids[$k]);
                        }
                    }
                }
                sleep(1);//让出1s时间给CPU
            }
        }
    
        /**
         * 创建worker进程,接受客户端连接
         */
        private function start_worker_process(){
            $pid = pcntl_fork();
            if($pid <0){
                exit("fork fail
    ");
            }elseif($pid > 0){
                $this->pids[] = $pid;
                // exit; //此处不可退出,否则Master进程就退出了
            }else{
                $this->acceptClient();
            }
        }
    
        private function acceptClient()
        {
            //子进程一直等待客户端连接,不能退出
            while(1){
                $conn = stream_socket_accept($this->socket, -1);
                if($this->onConnect) call_user_func($this->onConnect, $conn); //回调连接事件
    
                //开始循环读取消息
                $recv = ''; //实际收到消息
                $buffer = ''; //缓冲消息
                while(1){
                    $buffer = fread($conn, 20);
    
                    //没有收到正常消息
                    if($buffer === false || $buffer === ''){
                        if($this->onClose) call_user_func($this->onClose, $conn); //回调断开连接事件
                        break;//结束读取消息,等待下一个客户端连接
                    }
    
                    $pos = strpos($buffer, "
    "); //消息结束符
                    if($pos === false){
                        $recv .= $buffer;                            
                    }else{
                        $recv .= trim(substr($buffer, 0, $pos+1));
    
                        if($this->onMessage) call_user_func($this->onMessage, $conn, $recv); //回调收到消息事件
    
                        //客户端强制关闭连接
                        if($recv == "quit"){
                            echo "client close conn
    ";
                            fclose($conn);
                            break;
                        }
    
                        $recv = ''; //清空消息,准备下一次接收
                    }
                }
            }
        }
    
        function __destruct() {
            @fclose($this->socket);
        }
    }
    
    $server =  new TcpServer();
    
    $server->onConnect = function($conn){
        echo "onConnect -- accepted " . stream_socket_get_name($conn,true) . "
    ";
        fwrite($conn,"conn success
    ");
    };
    
    $server->onMessage = function($conn,$msg){
        echo "onMessage --" . $msg . "
    ";
        fwrite($conn,"received ".$msg."
    ");
    };
    
    $server->onClose = function($conn){
        echo "onClose --" . stream_socket_get_name($conn,true) . "
    ";
        fwrite($conn,"onClose "."
    ");
    };
    
    $server->run();
    

    运行:

    $ php process_multi.server.php 
    1528734803 Master process, pid 9110
    waiting client...
    

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

    $ ps -ef | grep php
    yjc       9110     1  0 00:33 ?        00:00:00 php process_multi.server.php
    yjc       9111  9110  0 00:33 ?        00:00:00 php process_multi.server.php
    yjc       9112  9110  0 00:33 ?        00:00:00 php process_multi.server.php
    yjc       9113  9110  0 00:33 ?        00:00:00 php process_multi.server.php
    yjc       9134  8589  0 00:35 pts/1    00:00:00 grep php
    

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

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

    $ telnet 127.0.0.1 9201
    Trying 127.0.0.1...
    Connected to 127.0.0.1.
    Escape character is '^]'.
    conn success
    hello server!
    received hello server!
    quit
    received quit
    Connection closed by foreign host.
    
  • 相关阅读:
    利用子查询解决复杂sql问题
    如何用临时表代替游标进行表记录的拷贝
    SQL新函数, 排名函数 ROW_NUMBER(), RANK(), DENSE_RANK()
    SQL SERVER2005 中的错误捕捉与处理
    用户自定义函数代替游标进行循环拼接
    使用游标进行循环数据插入
    Oracle中利用存储过程建表
    SQL SERVER中强制类型转换cast和convert的区别
    SQL如何修改被计算字段引用的字段类型
    1.官方网站:http://www.mplayerhq.hu/design7/dload.html
  • 原文地址:https://www.cnblogs.com/52fhy/p/9211505.html
Copyright © 2020-2023  润新知