调用外部程序
创建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。
管道类型可分为三种:
-
0
表示不创建管道 -
1
表示创建SOCK_STREAM
类型的管道 -
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_errno
和swoole_strerror
获取错误码和错误信息。 - 子进程会继承父进程的内存和文件句柄
- 子进程在启动时会清除从父进程继承的
EventLoop
、Signal
、Timer
- 执行后子进程会保持父进程的内存和资源
守护进程下,当执行完start
方法后发生了什么呢?
- 当前进程
fork
创建出Master主进程后退出,Master主进程触发onMasterStart
事件。 - Master主进程启动成功后会
fork
创建出Manager
进程并触发onManagerStart
事件 - 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