本文主要是介绍Swoole入门到实战(二):进程,内存和协程、Swoole完美支持ThinkPHP5、分发Task异步任务机制实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
一、进程,内存和协程
1.1 进程
1.1.1 进程
进程就是 正在运行的程序
的 一个实例
$process = new swoole_process(function(swoole_process $pro) {// todo
// php redis.php$pro->exec("/usr/local/php/bin/php", [__DIR__.'/../server/http_server.php']);
}, false);$pid = $process->start();
echo $pid . PHP_EOL;
//回收结束运行的子进程
swoole_process::wait();
以树状图显示进程间的关系:pstree -p 进程id
启动成功后会创建worker_num+2
个进程。Master
进程+Manager
进程+serv
->worker_num
个Worker
进程
1.1.2 进程使用场景
管道:进程和进程间的一个桥梁
echo "process-start-time:".date("Ymd H:i:s");
$workers = [];
$urls = ['http://baidu.com','http://sina.com.cn','http://qq.com','http://baidu.com?search=singwa','http://baidu.com?search=singwa2','http://baidu.com?search=imooc',
];
//创建多个子进程分别模拟请求URL的内容
for($i = 0; $i < 6; $i++) {$process = new swoole_process(function(swoole_process $worker) use($i, $urls) {// curl$content = curlData($urls[$i]);//将内容写入管道// echo $content.PHP_EOL;$worker->write($content.PHP_EOL);}, true);$pid = $process->start();$workers[$pid] = $process;
}
//获取管道内容
foreach($workers as $process) {echo $process->read();
}
/*** 模拟请求URL的内容 1s* @param $url* @return string*/
function curlData($url) {// curl file_get_contentssleep(1);return $url . "success".PHP_EOL;
}
echo "process-end-time:".date("Ymd H:i:s");
1.2 Swoole内存-table详解
内存操作模块之: Table
swoole_table
一个基于共享内存和锁实现的超高性能,并发数据结构
使用场景:用于解决多进程/多线程
数据共享和同步加锁问题
进程结束后内存表会自动释放
// 创建内存表
$table = new swoole_table(1024);// 内存表增加一列
$table->column('id', $table::TYPE_INT, 4);
$table->column('name', $table::TYPE_STRING, 64);
$table->column('age', $table::TYPE_INT, 3);
$table->create();$table->set('singwa_imooc', ['id' => 1, 'name'=> 'singwa', 'age' => 30]);
// 另外一种方案
$table['singwa_imooc_2'] = ['id' => 2,'name' => 'singwa2','age' => 31,
];$table->decr('singwa_imooc_2', 'age', 2);
print_r($table['singwa_imooc_2']);echo "delete start:".PHP_EOL;
$table->del('singwa_imooc_2');
print_r($table['singwa_imooc_2']);
1.3 协程
线程、进程、协程的区别
进程,线程,协程与并行,并发
并发与并行的区别?
$http = new swoole_http_server('0.0.0.0', 9001);$http->on('request', function($request, $response) {// 获取redis 里面 的key的内容, 然后输出浏览器$redis = new Swoole\Coroutine\Redis();$redis->connect('127.0.0.1', 6379);$value = $redis->get($request->get['a']);// mysql.....//执行时间取它们中最大的:time = max(redis,mysql)$response->header("Content-Type", "text/plain");$response->end($value);
});$http->start();
二、Swoole完美支持ThinkPHP5(重难点
)
2.1 面向过程方案
2.1.1 面向过程代码实现
$http = new swoole_http_server("0.0.0.0", 9911);$http->set(['enable_static_handler' => true,'document_root' => "/home/wwwroot/swoole/thinkphp/public/static",'worker_num' => 5,]
);
//此事件在Worker进程/Task进程启动时发生,这里创建的对象可以在进程生命周期内使用
$http->on('WorkerStart', function(swoole_server $server, $worker_id) {// 定义应用目录define('APP_PATH', __DIR__ . '/../../../../application/');// 加载框架里面的文件require __DIR__ . '/../../../../thinkphp/base.php';
});
$http->on('request', function($request, $response) use($http){//如果在每次请求时加载框架文件,则不用修改thinkphp5源码
// // 定义应用目录
// define('APP_PATH', __DIR__ . '/../../../../application/');
// // 加载框架里面的文件
// require_once __DIR__ . '/../../../../thinkphp/base.php';/*** 解决上一次输入的变量还存在的问题* 方案一:if(!empty($_GET)) {unset($_GET);}* 方案二:$http-close();把之前的进程kill,swoole会重新启一个进程,重启会释放内存,把上一次的资源包括变量等全部清空* 方案三:$_SERVER = []*/$_SERVER = [];if(isset($request->server)) {foreach($request->server as $k => $v) {$_SERVER[strtoupper($k)] = $v;}}if(isset($request->header)) {foreach($request->header as $k => $v) {$_SERVER[strtoupper($k)] = $v;}}$_GET = [];if(isset($request->get)) {foreach($request->get as $k => $v) {$_GET[$k] = $v;}}$_POST = [];if(isset($request->post)) {foreach($request->post as $k => $v) {$_POST[$k] = $v;}}//开启缓冲区ob_start();// 执行应用并响应try {think\Container::get('app', [APP_PATH])->run()->send();}catch (\Exception $e) {// todo}//输出TP当前请求的控制方法//echo "-action-".request()->action().PHP_EOL;//获取缓冲区内容$res = ob_get_contents();ob_end_clean();$response->end($res);//把之前的进程kill,swoole会重新启一个进程,重启会释放内存,把上一次的资源包括变量等全部清空//$http->close();
});
$http->start();
测试:
2.1.2 onWorkerStart事件
//此事件在Worker进程/Task进程启动时发生,这里创建的对象可以在进程生命周期内使用
$http->on('WorkerStart', function(swoole_server $server, $worker_id) {// 定义应用目录define('APP_PATH', __DIR__ . '/../../../../application/');// 加载框架里面的文件require __DIR__ . '/../../../../thinkphp/base.php';
});
Tips:如果修改了加载框架文件,需要重启: php php_server.php
onWorkerStart:
此事件在Worker
进程/Task
进程启动时发生,这里创建的对象可以在进程生命周期内使用
在onWorkerStart
中加载框架的核心文件后:
- 不用每次请求都加载框架核心文件,提高性能
- 可以在后续的回调事件中继续使用框架的核心文件或者类库
2.1.3 关于再次请求进程缓存解决方案
当前worker
进程没有结束,所以会保存上一次的资源等。解决上一次输入的变量还存在的问题:
- 方案一:
if(!empty($_SERVER)) { unset($_SERVER); }
- 方案二:
$http-close();
把之前的进程kill
,swoole
会重新启一个进程,重启会释放内存,把上一次的资源包括变量等全部清空(php-cli
控制台会提示错误) - 方案三:
$_SERVER = []
(推荐方案)
2.1.3 关于ThinkPHP5路由解决方案
当第一次请求后下一次再请求不同的模块或者方法不生效,都是‘第一次’请求 模块/控制器/方法。如下图:
修改ThinkPHP5
框架Request.php
源码位置:/thinkphp/library/think/Request.php
修改如下:
function path() { }
//注销判断,不再复用类成员变量$this->path
function pathinfo() { }
//注销判断,不再复用类成员变量$this->pathinfo
- 使其支持
pathinfo
路由,添加如下代码在function pathinfo() { }
中
if (isset($_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] != '/') {return ltrim($_SERVER['PATH_INFO'], '/');}
修改后完整Request.php
文件:
/*** 获取当前请求URL的pathinfo信息(含URL后缀)* @access public* @return string*/public function pathinfo(){if (isset($_SERVER['PATH_INFO']) && $_SERVER['PATH_INFO'] != '/') {return ltrim($_SERVER['PATH_INFO'], '/');}
// if (is_null($this->pathinfo)) {if (isset($_GET[$this->config->get('var_pathinfo')])) {// 判断URL里面是否有兼容模式参数$_SERVER['PATH_INFO'] = $_GET[$this->config->get('var_pathinfo')];unset($_GET[$this->config->get('var_pathinfo')]);} elseif ($this->isCli()) {// CLI模式下 index.php module/controller/action/params/...$_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';}// 分析PATHINFO信息if (!isset($_SERVER['PATH_INFO'])) {foreach ($this->config->get('pathinfo_fetch') as $type) {if (!empty($_SERVER[$type])) {$_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ?substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type];break;}}}$this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/');
// }return $this->pathinfo;}/*** 获取当前请求URL的pathinfo信息(不含URL后缀)* @access public* @return string*/public function path(){//注销判断,不再复用类成员变量$this->path
// if (is_null($this->path)) {$suffix = $this->config->get('url_html_suffix');$pathinfo = $this->pathinfo();if (false === $suffix) {// 禁止伪静态访问$this->path = $pathinfo;} elseif ($suffix) {// 去除正常的URL后缀$this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo);} else {// 允许任何后缀访问$this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo);}
// }return $this->path;}
2.2 面向对象方案
class Http {CONST HOST = "0.0.0.0";CONST PORT = 9911;public $http = null;public function __construct() {$this->http = new swoole_http_server(self::HOST, self::PORT);$this->http->set(['enable_static_handler' => true,'document_root' => "/home/wwwroot/swoole/thinkphp/public/static",'worker_num' => 4,]);$this->http->on("workerstart", [$this, 'onWorkerStart']);$this->http->on("request", [$this, 'onRequest']);$this->http->on("close", [$this, 'onClose']);$this->http->start();}/*** 此事件在Worker进程/Task进程启动时发生,这里创建的对象可以在进程生命周期内使用* 在onWorkerStart中加载框架的核心文件后* 1.不用每次请求都加载框架核心文件,提高性能* 2.可以在后续的回调中继续使用框架的核心文件或者类库** @param $server* @param $worker_id*/public function onWorkerStart($server, $worker_id) {// 定义应用目录define('APP_PATH', __DIR__ . '/../../../../application/');// 加载框架里面的文件require __DIR__ . '/../../../../thinkphp/base.php';}/*** request回调* 解决上一次输入的变量还存在的问题例:$_SERVER = []* @param $request* @param $response*/public function onRequest($request, $response) {$_SERVER = [];if(isset($request->server)) {foreach($request->server as $k => $v) {$_SERVER[strtoupper($k)] = $v;}}if(isset($request->header)) {foreach($request->header as $k => $v) {$_SERVER[strtoupper($k)] = $v;}}$_GET = [];if(isset($request->get)) {foreach($request->get as $k => $v) {$_GET[$k] = $v;}}$_POST = [];if(isset($request->post)) {foreach($request->post as $k => $v) {$_POST[$k] = $v;}}$_POST['http_server'] = $this->http;ob_start();// 执行应用并响应try {think\Container::get('app', [APP_PATH])->run()->send();}catch (\Exception $e) {// todo}$res = ob_get_contents();ob_end_clean();$response->end($res);}/*** close* @param $ws* @param $fd*/public function onClose($ws, $fd) {echo "clientid:{$fd}\n";}
}
new Http();
三、分发Task异步任务机制实现
示例演示:发送验证码
1、优化,将对接第三方的接口放入异步任务中
$_POST['http_server']->task($taskData);
/*** 发送验证码*/public function index() {$phoneNum = intval($_GET['phone_num']);// tp inputif(empty($phoneNum)) {return Util::show(config('code.error'), 'error');}// 生成一个随机数$code = rand(1000, 9999);$taskData = ['method' => 'sendSms','data' => ['phone' => $phoneNum,'code' => $code,]];//优化,将对接第三方的接口放入异步任务中$_POST['http_server']->task($taskData);return Util::show(config('code.success'), 'ok');}
}
2、将http
对象放入预定义$_POST
中,传给调用者
$_POST['http_server'] = $this->http;
/*** request回调*/public function onRequest($request, $response) {......//将http对象放入预定义$_POST中,传给调用者$_POST['http_server'] = $this->http;ob_start();// 执行应用并响应try {think\Container::get('app', [APP_PATH])->run()->send();}catch (\Exception $e) {// todo}......}
3、Task任务分发
/*** Task任务分发*/public function onTask($serv, $taskId, $workerId, $data) {// 分发 task 任务机制,让不同的任务 走不同的逻辑$obj = new app\common\lib\task\Task;$method = $data['method'];$flag = $obj->$method($data['data']);return $flag; // 告诉worker}
4、代表的是swoole
里面后续所有task
异步任务都放这里来
class Task {/*** 异步发送 验证码*/public function sendSms($data, $serv) {try {$response = Sms::sendSms($data['phone'], $data['code']);}catch (\Exception $e) {return false;}// 如果发送成功 把验证码记录到redis里面if($response->Code === "OK") {Predis::getInstance()->set(Redis::smsKey($data['phone']), $data['code'], config('redis.out_time'));}else {return false;}return true;}
}
这篇关于Swoole入门到实战(二):进程,内存和协程、Swoole完美支持ThinkPHP5、分发Task异步任务机制实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!