索引:
第一部分 让TP5初步支持Swoole
其实有一个thinkphp/swoole(好像叫这个名字,可以用composer安装),还有一个是easyswoole的框架也可以用。但是这里以TP5为例,从原理上说明如何让Swoole支持框架,涉及到框架本身以及Swoole的一些机制,比如不会自动unset等等,可以外yii或者其他框架上进行扩展以实现一个高并发的处理机制。
使用的是ThinkPHP5.1.0RC1核心版,这个是目前的最新版,其他版本思路都是一样的。
将前端文件放在static -> live目录下面,新建一个文件夹放在,这个server是目录之前没有的,这里用于放和服务器有关的代码 thinkphp -> server->http_server.php
http_server.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?php /** * Created by bingxiong. * Date: 4/15/18 * Time: 4:16 PM * Description: */ $http = new swoole_http_server("0.0.0.0",8811); $http->set([ 'enable_static_handler' => true, // 'document_root' => "/Users/bingxiong/swoole/hdtocs/thinkphp/public/static" // 设置静态资源默认存放路径 ]); $http->on('request',function ($request,$response){ }); $http->start(); |
注意:指定的静态资源存放路径要设置正确,这个路径是刚才放置前端文件的路径,注意端口号。
命令行启动http_server.php
1 2 3 |
MacBook-Pro:server bingxiong$ pwd /Users/bingxiong/swoole/hdtocs/thinkphp/server MacBook-Pro:server bingxiong$ php http_server.php |
这个时候通过刚才设置的端口号启动 http://localhost:8811/live/login.html 页面就显示出来了,已经初步的支持了,但是还有非常多的问题需要考虑。
第二部分 更好的支持Swoole
Swoole中有一些方法比如get、post、server之类的和原生的php不一样,所以必须让Swoole去适配TP框架
或者说要使用http://localhost:8811/live/index/index/test方法,不去修改是找不到的。
首先明确一点,要去请求TP首先会访问public -> index.php的这个入口文件。在swoole中有一个onWorkerStart的事件,这个事件在Worker进程 / Task 进程启动时发生。这里创建的对象可以在进程的生命周期内使用。也就说是启动http服务的时候.也就是说在启动的时候要把TP的一些加载内容放在worker进程里面去。
thinkphp -> public ->index.php
1 2 3 4 5 6 7 |
<?php // [ 应用入口文件 ] // 定义应用目录 define('APP_PATH', __DIR__ . '/../application/'); // 加载框架引导文件 require __DIR__ . '/../thinkphp/start.php'; |
可以看到实际上tp加载的是start.php,来看一下start.php
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?php namespace think; // ThinkPHP 引导文件 // 加载基础文件 require __DIR__ . '/base.php'; // 支持事先使用静态方法设置Request对象和Config对象 // 执行应用并响应 Container::get('app', [defined('APP_PATH') ? APP_PATH : '']) ->run() ->send(); |
可以看到start.php实际上就是加载了一个base.php这样一个核心的文件。可就是说只要把这个base.php加到onWorkerStart的事件回调里面去,这样每个worker的进程就会先加载TP框架的内容了,相当于热加载,直接放在worker里面就启动了。但是非常重要的一点,我们不能够直接加载
1 |
require __DIR__ . '/../thinkphp/start.php'; |

1 2 3 4 |
// 执行应用并响应 Container::get('app', [defined('APP_PATH') ? APP_PATH : '']) ->run() ->send(); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<?php /** * Created by bingxiong. * Date: 4/15/18 * Time: 4:16 PM * Description: */ $http = new swoole_http_server("0.0.0.0",8811); $http->set([ 'enable_static_handler' => true, // 'document_root' => "/Users/bingxiong/swoole/hdtocs/thinkphp/public/static", // 设置静态资源默认存放路径 'worker_num' => 5, ]); $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){ }); $http->start(); |
接下来要做适配,因为swoole的http服务获取参数的方法和原生的php是不一样的,而tp是基于原生的php去写的因此需要做一个装换。然后把真正执行TP框架的代码放进来,这里需要使用ob_start()等来把这段真正执行TP框架的代码做一个缓冲区,所以现在成了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
<?php /** * Created by bingxiong. * Date: 4/15/18 * Time: 4:16 PM * Description: */ $http = new swoole_http_server("0.0.0.0",8811); $http->set([ 'enable_static_handler' => true, // 'document_root' => "/Users/bingxiong/swoole/hdtocs/thinkphp/public/static", // 设置静态资源默认存放路径 'worker_num' => 5, ]); $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){ // 将swoole中一些特别的用法装换成原生的php if(isset($request->server)){ foreach ($request->server as $k => $v){ $_SERVER[strtoupper($k)] = $v; } } if(isset($request->get)){ foreach ($request->get as $k => $v){ $_GET[$k] = $v; } } if(isset($request -> post)){ foreach ($request->server as $k => $v){ $_POST[$k] = $v; } } // 执行框架中的内容 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); }); $http->start(); |
这个时候就可以使用TP5模块/控制器/方法名的方式来进行访问了,但是下一个问题又来了,如果进行get操作的话每次get的值删不掉,如图
出现这个的问题是在swoole里面超全局变量只要进程还在是不会释放的。因此需要做一个判断来unset
1 2 3 |
if(!empty($_GET)){ unset($_GET); } |
这样就好了,这是一个非常重要的问题,post、get、cookie这些都需要这样来写来判断然后注销,还有就是define来定义的也是不会注销的,另外exit也是需要谨慎的。
另外还有一个很可怕的地方就是如果index控制器下面有另一个方法目前是走不到的还是index方法,这里的原因是TP中其实是会把控制器、模块以及方法放在变量中去,这个值在worker进程中是不会被注销掉的,这样的话就会导致下一个访问的时候还是上次的,所以走不到新的路径。在这里我们使用一种很粗暴的方式来unset()所有变量包括get、post就是关闭http服务器,当然这种方式太过于粗暴,之后我们会对这种做法进行优化。然后就可以访问index控制器下的bing方法了(注意这里是nginx的访问方式)
目前运行控制台会报错,并且不支持TP原生的方式访问,在下一部分中会解决这些问题,让Swoole完美支持TP。
目前一部分的最终代码:
http_server.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
<?php /** * Created by bingxiong. * Date: 4/15/18 * Time: 4:16 PM * Description: */ $http = new swoole_http_server("0.0.0.0",8811); $http->set([ 'enable_static_handler' => true, // 'document_root' => "/Users/bingxiong/swoole/hdtocs/thinkphp/public/static", // 设置静态资源默认存放路径 'worker_num' => 5, ]); $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){ // 将swoole中一些特别的用法装换成原生的php if(isset($request->server)){ foreach ($request->server as $k => $v){ $_SERVER[strtoupper($k)] = $v; } } if(isset($request->get)){ foreach ($request->get as $k => $v){ $_GET[$k] = $v; } } if(isset($request -> post)){ foreach ($request->server as $k => $v){ $_POST[$k] = $v; } } // 执行框架中的内容 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); $http->close(); }); $http->start(); |
第三部分 让Swoole完美支持TP
之前用了$http->close(),其实这样是不可取的,是会报错的,之前的进程就会被kill然后再次请求后就会关闭进程,其实在swoole中写die或者exit也会让进程直接挂掉,这是swoole内部的重启机制。这样做相当于每次重启,相当于每次重新请求新的东西。接下来就需要修改thinkphp框架中的内容了。
首先需要定位问题出现的位置,在加载tp框架的代码中有这样一段代码:
1 2 3 |
think\Container::get('app', [APP_PATH]) ->run() ->send(); |
可以看到执行TP是通过run方法来执行的,我们定位到run方法,在library->think->app.php里面,而在run方法中还有一个方法是routeCheck进行URL路由检测,问题就出现在这里的$path,而path是在library->think->Request.php里面,在path中还使用了pathinfo这个方法就在path方法中的上面,修改这两个的判断条件注销掉就可以了:
Request.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
/** * 获取当前请求URL的pathinfo信息(含URL后缀) * @access public * @return string */ public function pathinfo() { // 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() { // 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; } |
这样就可以直接以TP的url访问,每次都不会走到之前的url。接下来修改http_server,删除$http->close(),并且用更好的方式把get、post、server等注销掉,就是每次赋值为空,不需要使用unset。
http_server.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
<?php /** * Created by bingxiong. * Date: 4/15/18 * Time: 4:16 PM * Description: */ $http = new swoole_http_server("0.0.0.0",8811); $http->set([ 'enable_static_handler' => true, // 'document_root' => "/Users/bingxiong/swoole/hdtocs/thinkphp/public/static", // 设置静态资源默认存放路径 'worker_num' => 5, ]); $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){ // 将swoole中一些特别的用法装换成原生的php $_SERVER =[]; if(isset($request->server)){ foreach ($request->server 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->server as $k => $v){ $_POST[$k] = $v; } } // 执行框架中的内容 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); }); $http->start(); |
效果:
但是目前我们是用普通方式来访问的,还不能够以TP的方式来访问,这就需要修改TP的源码了,问题出现在pathinfo方法,我们需要让其适配url模式,然后就得到了最终的修改文件,这里我把修改的两个文件一个是http_server.php这是开启swoole http服务的代码,然后修改的request.php的代码放在这里:
最终代码:
Request.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
/** * 获取当前请求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() { // 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; } |
http_server.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
<?php /** * Created by bingxiong. * Date: 4/15/18 * Time: 4:16 PM * Description: */ $http = new swoole_http_server("0.0.0.0",8811); $http->set([ 'enable_static_handler' => true, // 'document_root' => "/Users/bingxiong/swoole/hdtocs/thinkphp/public/static", // 设置静态资源默认存放路径 'worker_num' => 5, ]); $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){ // 将swoole中一些特别的用法装换成原生的php $_SERVER =[]; if(isset($request->server)){ foreach ($request->server 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->server as $k => $v){ $_POST[$k] = $v; } } // 执行框架中的内容 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); }); $http->start(); |