本节讲解几个多进程的实例.
多进程实例
Master-Worker 结构
下面例子实现了简单的多进程管理:
支持设置最大子进程数
Master-Worker 结构: Worker 挂掉, Master 进程会重新创建一个
- <?php
- $pids = []; // 存储子进程 pid
- $MAX_PROCESS = 3;// 最大进程数
- $pid = pcntl_fork();
- if($pid <0){
- exit("fork fail\n");
- }elseif($pid> 0){
- exit;// 父进程退出
- }else{
- // 从当前终端分离
- if (posix_setsid() == -1) {
- die("could not detach from terminal");
- }
- $id = getmypid();
- echo time()."Master process, pid {$id}\n";
- 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... \n";
- start_worker_process();
- unset($pids[$pid]);
- }
- }
- }
- }
- }
- /**
- * 创建 worker 进程
- */
- function start_worker_process(){
- global $pids;
- $pid = pcntl_fork();
- if($pid <0){
- exit("fork fail\n");
- }elseif($pid> 0){
- $pids[$pid] = $pid;
- // exit; // 此处不可退出, 否则 Master 进程就退出了
- }else{
- // 实际代码
- $id = getmypid();
- $rand = rand(1,3);
- echo time()."Worker process, pid {$id}. run $rand s\n";
- 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\n");
- }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}\n";
- // 创建 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...\n";
- //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... \n";
- $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\n");
- }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, "\n"); // 消息结束符
- 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\n";
- 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) . "\n";
- fwrite($conn,"conn success\n");
- };
- $server->onMessage = function($conn,$msg){
- echo "onMessage --" . $msg . "\n";
- fwrite($conn,"received".$msg."\n");
- };
- $server->onClose = function($conn){
- echo "onClose --" . stream_socket_get_name($conn,true) . "\n";
- fwrite($conn,"onClose"."\n");
- };
- $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.
来源: https://juejin.im/entry/5b2c4186e51d4558c47219fd