Swoole进程操作

调用外部程序

创建WebSocket服务器

$ vim server.php
<?php
$ws = new swoole_websocket_server("0.0.0.0", 9701);
$ws->on("open", function($ws, $request){
    $ws->push($request->fd, "connect success");
});
$ws->on("close", function($ws, $fd){
    echo "fd: {$fd}  close";
});
$ws->on("message", function($ws, $frame){
    $ws->push($frame->fd, "server:".$frame->data);
});
$ws->start();

创建WebSocket客户端

$ vim client.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <script>
        var ws = new WebSocket("ws://192.168.99.100:9701");
        ws.onmessage = function(evt){
            console.log(evt.data);
        };
    </script>
</body>
</html>

创建进程

$ vim process.php

可以在swoole_process创建子进程中使用swoole_server,但为了安全必须在$process->start创建进程后调用$worker->exec()执行。

<?php
//进程逻辑处理
function handle(swoole_process $worker){
    //在子进程中创建服务
    $worker->exec("/usr/local/bin/php", [__DIR__."/server.php"]);
}
//创建子进程
$process = new swoole_process("handle", true);//参数true表示不打印输出到终端
//启动子进程
$pid = $process->start();
echo $pid;
//回收结束运行的子进程
swoole_process::wait();

使用匿名函数作为进程逻辑,并实现简单的父子进程通信。

<?php
//创建子进程
$process = new swoole_process(function(swoole_process $worker){
    //在子进程中创建服务
    $worker->exec("/usr/local/bin/php", [__DIR__."/server.php"]);
}, true);//参数true表示不打印输出到终端
//启动子进程
$pid = $process->start();
echo $pid;
//回收结束运行的子进程
swoole_process::wait();

运行脚本

$ php process.php
46

打印出来的46表示子进程PID,此时WebSocket服务器已经运行,它是由process.php的子进程46开启的。

查看process.php程序的进程PID

$ ps aux | grep process.php
root        45  0.0  3.0 340468 30732 pts/1    S+   12:31   0:00 php process.php
root        56  0.0  0.0  11112   948 pts/2    S+   12:32   0:00 grep process.php

查看process.php下所有进程之间的关系

$ pstree -p 45
php(45)---php(46)-+-php(47)---php(49)
                  `-{php}(48)

查看server.php的进程关系

$ ps aft | grep server.php
   61 pts/2    S+     0:00  \_ grep server.php
   46 pts/1    Sl+    0:00      \_ /usr/local/bin/php /app/server.php
   47 pts/1    S+     0:00          \_ /usr/local/bin/php /app/server.php
   49 pts/1    S+     0:00              \_ /usr/local/bin/php /app/server.php

创建进程 Process::__construct

  • swoole_process是Swoole提供的进程管理模块,用来替代PHP的pcntl扩展。
  • swoole_process在Swoole1.8中已经禁止在Web环境下使用,只能在命令行中使用。如果需要做并发,multi-curl是一个不错的选择。

原型

swoole_process::__construct(
callable $function, 
bool $redirect_stdin_stdout = false, 
int $pipe_type = SOCK_DGRAM, 
bool $enable_coroutine = false
);

参数

  • callable $function
    子进程创建成功后执行的回调函数,底层会自动将函数保存到对象的callback属性上,如果需要更改执行的函数,可赋值新的函数到对象的callback属性上。
  • bool $redirect_stdin_stdout
    重定向子进程的标准输入和输出,启动此选项后,在子进程内输出内容将不是打印到屏幕,而是写入到主进程管道,读取键盘输入将变为从管道中读取数据,默认为阻塞读取。
  • int $pipe_type
    管道类型,启用$redirect_stdin_stdout后,此选项将忽略用户参数,强制为1。如果子进程内没有进程间通信,可设置为0。

管道类型可分为三种:

  1. 0表示不创建管道
  2. 1表示创建SOCK_STREAM类型的管道
  3. 2表示创建SOCK_DGRAM类型的管道

当启用$redirect_stdin_stdout后,此选项将忽略用户参数,强制为1。

Process对象在销毁时会自动关闭管道,子进程内如果监听了管道会收到CLOSE事件,使用Process作为监控父进程,创建管理子进程时,父类必须注册信号SIGCHLD对退出的进程执行wait,否则子进程退出时会变成僵尸进程。

  • bool $enable_coroutine
    是否在回调函数中开启协程,默认为false。开启后可以直接在进程的函数中使用协程API。

进程别名Process->name

  • name方法用于修改进程名称,适用于1.7.9+版本。
  • name方法应当在start之后的子进程回调函数中使用
  • name方法是swoole_set_process_name函数的别名
  • 当执行exec后程序名称才会被新的替换

函数

swoole_set_process_name(string $name);

方法

$process->name(string $name);

启动进程Process->start

start方法用于执行fork系统调用派生创建子进程,也就是启动进程。

函数原型

function swoole_process->start():int

返回值

  • 创建成功返回子进程的PID,创建失败返回false
  • 可使用swoole_errnoswoole_strerror获取错误码和错误信息。
  • 子进程会继承父进程的内存和文件句柄
  • 子进程在启动时会清除从父进程继承的EventLoopSignalTimer
  • 执行后子进程会保持父进程的内存和资源

守护进程下,当执行完start方法后发生了什么呢?

  1. 当前进程fork创建出Master主进程后退出,Master主进程触发onMasterStart事件。
  2. Master主进程启动成功后会fork创建出Manager进程并触发onManagerStart事件
  3. Manager管理进程启动成功时会fork创建出Worker工作进程并触发onWorkerStart事件

执行程序Process->exec

exec方法用于执行一个外部程序,是对exec系统调用的封装。

原型

bool Process->exec(string $execfile, array $args)

参数

  • string $execfile 指定可执行文件的绝对路径,若不是绝对路径则会报文件不存在错误。
  • array $args 指定exec的参数列表,格式为数组。

结果

exec执行成功后当前进程代码段将会新程序替代,子进程将蜕变成另一套程序,父进程与当前进程仍然是父子进程关系。父进程与新进程之间可通过标准输入输出进行通信,必须启动标准输入输出重定向。

注意

由于exec系统调用会使用指定的程序覆盖当前程序,子进程需要读写标准输出与父进程进行通信。若swoole_process未指定redirect_stdin_stdout = true则执行exec后子进程与父进程将无法通信。

回收进程Process::wait

回收结束运行的子进程

原型

array Process::wait(bool $blocking = true);

参数

  • $blocking可指定是否阻塞等待,默认未阻塞。

返回

操作成功会返回一个数组包含子进程的PID、退出状态码、被哪种信号KILL,失败则返回false

注意

  • 子进程结束后必须执行wait进行回收,若不回收则会变成僵尸进程。
  • 使用Process作为监控父进程,创建管理子进程时父类必须注册信号SIGCHLD对退出的进程执行wait,否则子进程一旦被kill就会引发父进程退出。

进程操作

$ vim  base.php
<?php
use Swoole\Process;
//创建子进程
$process = new Process(function(Process $worker){
    //判断当前进程是否存在
    //kill操作用于杀死进程传入0表示检测进程是否存在
    if(Process::kill($worker->pid, 0)){
        //退出子进程
        $worker->exit();
    }
});
//启动子进程
$process->start();
//回收退出的子进程
Process::wait();
  • new Process() 通过回调函数设置子进程将要执行的逻辑
  • $process->start() 调用fork()系统调用用来生成子进程
  • Process::kill() 给进程发送信号用于杀死进程,传入0表示检测进程是否存在。
  • Process::wait() 调用wait()系统调用用来回收子进程,如果不回收子进程会变成僵尸进程,浪费系统资源。
  • $worker->exit() 子进程主动退出

例如:主进程退出子进程执行完后也退出,子进程异常退出主进程自动重启。

$ vim master.php
<?php
use Swoole\Process;

class MasterProcess
{
    //主进程PID,即当前程序的进程ID。
    public $pid = 0;
    //进程最大数量
    public $max_process_num = 1;
    //记录子进程的索引
    public $index = 0;
    //记录子进程的PID
    public $works = [];

    /**构造函数 */
    public function __construct()
    {
        try{
            swoole_set_process_name(__CLASS__.":master");
            $this->pid = posix_getpid();
            $this->run();
            $this->wait();
        }catch(Exception $ex){
            die("ERROR:".$ex->getMessage());
        }
    }
    /**循环创建子进程*/
    public function run()
    {
        for($i=0; $i<$this->max_process_num; $i++){
            $this->create();
        }
    }
    /**逐个创建进程 */
    public function create($index = null)
    {
        if(is_null($index)){
            $index = $this->index;
            $this->index++;
        }
        $process = new Process(function(Process $worker) use($index){
            swoole_set_process_name(__CLASS__.": worker {$index}");
            //模拟子进程执行消耗任务
            for($i=0; $i<3; $i++){
                $this->check($worker);
                echo "check  pid : {$i}\n";
                sleep(1);
            }
        }, false, false);//不重定向输入输出,不使用管道
        //创建进程
        $pid = $process->start();
        $this->works[$index] = $pid;
        return $pid;
    }
    /**主进程异常退出 子进程工作完成后退出 */
    public function check(Process $worker)
    {
        //检测进程是否存在
        if(!Process::kill($this->pid, 0)){
            echo "master process exited, worker {$worker->pid} also exit\n";
            $worker->exit();
        }
    }
    /**重启子进程 */
    public function reboot($pid)
    {
        $index = array_search($pid, $this->works);
        if($index !== false){
            $newpid = $this->create($index);
            echo "reboot process: {$index} = {$pid}->{$newpid} \n";
            return;
        }
        throw new Exception("reboot process error: no pid {$pid}");
    }
    /**自动重启子进程 */
    public function wait()
    {
        while(1){
            if(!count($this->works)){
                break;
            }
            //若子进程退出则重启
            if($ret = Process::wait()){
                $this->reboot($ret["pid"]);
            }
        }
    }
}

new MasterProcess();

运行程序

$ php master.php
check  pid : 0
check  pid : 1
check  pid : 2
reboot process: 0 = 148->150
check  pid : 0
check  pid : 1
check  pid : 2
...

查看进程

$ ps aux|grep MasterProcess
root       147  0.1  3.0 340468 31176 pts/1    S+   14:23   0:00 MasterProcess: master
root       155  0.0  1.0 342520 10960 pts/1    S+   14:23   0:00 MasterProcess: worker 0
root       157  0.0  0.0  11112   996 pts/2    S+   14:23   0:00 grep MasterProcess

杀死主进程子进程执行完毕后退出

$ kill -9 147

杀死子进程

$ kill -9 155
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容