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

多进程实例

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.

PHP多进程系列笔记(三)的更多相关文章

  1. PHP多进程系列笔记(五)

    前面几节都是讲解pcntl扩展实现的多进程程序.本节给大家介绍swoole扩展的swoole_process模块. swoole多进程 swoole_process 是swoole提供的进程管理模块, ...

  2. PHP多进程系列笔记(二)

    上一篇文章讲解了pcntl_fork和pcntl_wait两个函数的使用,本篇继续讲解PHP多进程相关新知识. 僵尸(zombie)进程 这里说下僵尸进程: 僵尸进程是指的父进程已经退出,而该进程de ...

  3. PHP多进程系列笔记(一)

    本系列文章将向大家讲解pcntl_*系列函数,从而更深入的理解进程相关知识. PCNTL在PHP中进程控制支持默认是关闭的.您需要使用 --enable-pcntl 配置选项重新编译PHP的 CGI或 ...

  4. PHP多进程系列笔记(四)

    本节主要讲解Posix常用函数和进程池的概念,也会涉及到守护进程的知识.本节难度较低. Posix常用函数 posix_kill 向指定pid进程发送信号.成功时返回 TRUE , 或者在失败时返回 ...

  5. 《Java 8实战》读书笔记系列——第三部分:高效Java 8编程(四):使用新的日期时间API

    https://www.lilu.org.cn/https://www.lilu.org.cn/ 第十二章:新的日期时间API 在Java 8之前,我们常用的日期时间API是java.util.Dat ...

  6. 【原】Learning Spark (Python版) 学习笔记(三)----工作原理、调优与Spark SQL

    周末的任务是更新Learning Spark系列第三篇,以为自己写不完了,但为了改正拖延症,还是得完成给自己定的任务啊 = =.这三章主要讲Spark的运行过程(本地+集群),性能调优以及Spark ...

  7. Java系列笔记(4) - JVM监控与调优

    目录 参数设置收集器搭配启动内存分配监控工具和方法调优方法调优实例     光说不练假把式,学习Java GC机制的目的是为了实用,也就是为了在JVM出现问题时分析原因并解决之.通过学习,我觉得JVM ...

  8. 跟着鸟哥学Linux系列笔记1

    跟着鸟哥学Linux系列笔记0-扫盲之概念 跟着鸟哥学Linux系列笔记0-如何解决问题 装完linux之后,接下来一步就是进行相关命令的学习了 第五章:首次登录与在线求助man page 1. X ...

  9. 《MFC游戏开发》笔记三 游戏贴图与透明特效的实现

    本系列文章由七十一雾央编写,转载请注明出处. 313239 作者:七十一雾央 新浪微博:http://weibo.com/1689160943/profile?rightmod=1&wvr=5 ...

随机推荐

  1. Redis-4.0.11集群配置

    版本:redis-3.0.5 redis-3.2.0  redis-3.2.9  redis-4.0.11 参考:http://redis.io/topics/cluster-tutorial. 集群 ...

  2. POJ1195--Mobile phones(基础二维BIT)

    Description Suppose that the fourth generation mobile phone base stations in the Tampere area operat ...

  3. android 屏幕旋转 不重新加载oncreate

    当手机设定了使用横屏或者竖屏的时候,还想要使用重力感应,可以设置activity属性 android:screenOrientation="sensor" 但是每次翻转屏幕,都会重 ...

  4. Linux 修改默认的 yum 源

    官方的yum源在国内访问效果不佳. 需要改为国内比较好的阿里的 yum源,因为每次装的时候都得百度,所以这里记录一下. 修改方式: 1)cd /etc/yum.repos.d/ 这个目录下普通用户可能 ...

  5. shell 中的 && 和 ||

    shell 中的 && 和 || 简言之,shell 中 && --左边的命令执行成功才会执行右边的命令. || -- 左边的命令执行失败才会执行右边的命令.

  6. Oracle EBS主界面的Top Ten List

    http://blog.csdn.net/pan_tian/article/details/7749128 Top Ten List的数据保存在表FND_USER_DESKTOP_OBJECTS中,登 ...

  7. Oracle EBS登陆后,直接打开某个特定Form/Page

    http://blog.csdn.net/pan_tian/article/details/8169339 有一个小技巧,Oracle EBS登陆后可以绕过职责和功能的选择过程,就可以直接打开某个特定 ...

  8. [Proposal]Transform ur shapes!

    [Name] Transform ur shapes [Motivation]市场上有很多涂鸦游戏,例如火柴人涂鸦,非常有趣 我们可以结合所学,将一些图形变形的操作融入进去,做一个我们自己的有趣的游戏 ...

  9. .NET Core下开源任务调度框架Hangfire的Api任务拓展(支持秒级任务)

    HangFire的拓展和使用 看了很多博客,小白第一次写博客. 最近由于之前的任务调度框架总出现问题,因此想寻找一个替代品,之前使用的是Quartz.Net,这个框架方便之处就是支持cron表达式适合 ...

  10. MVC 5使用ViewBag(对象)显示数据

    前面Insus.NET有演示使用ViewData来实现控制器与视图的通讯.如果想了解的话,可以从下面两个链接可以查看:<MVC 5使用ViewData(对象)显示数据>http://www ...