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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容