ThinkPHP即将迎来最新版本6.0,针对目前越来越流行Swoole,thinkphp也推出了最新的扩展think-swoole 3.0
tp-swoole3.0不同于2.0版本,采用了全新的架构。(如下图目录结构)
tp主要针对的是非常驻内存方式运行,为了兼容swoole,虽然做了很多优化,但是仍然无法像swoft,sd等一些针对swoole开发的框架一样。这里所说的不同,不是指tp不好,而是因为两种模式都要兼容,不得不做出一些取舍。
分析该框架的运行机制,其实主要分析swoole的OnRequest函数即可,路由分发,数据处理等都是在函数处进行处理的。
Swoole.php
public function onRequest($req, $res) { $this->app->event->trigger('swoole.request'); $this->resetOnRequest(); /** @var Sandbox $sandbox */ $sandbox = $this->app->make(Sandbox::class); $request = $this->prepareRequest($req); try { $sandbox->setRequest($request); $sandbox->init(); $response = $sandbox->run($request); $this->sendResponse($sandbox, $response, $res); } catch (Throwable $e) { try { $exceptionResponse = $this->app ->make(Handle::class) ->render($request, $e); $this->sendResponse($sandbox, $exceptionResponse, $res); } catch (Throwable $e) { $this->logServerError($e); } } finally { $sandbox->clear(); } }
函数初始处,触发了一个request事件,这里方便用户自定义处理请求,进行一些定制化处理
$this->app->event->trigger('swoole.request');
重置请求,当是Websocket的时候,重置该类,具体为什么,下次我们分析Websocket的时候在进行解释
$this->resetOnRequest(); protected function resetOnRequest() { // Reset websocket data if ($this->isServerWebsocket) { $this->app->make(Websocket::class)->reset(true); } }
接下来通过容器获取沙盒,这里也是关键之处。在非常住内存框架中,为了方便会有一些写法导致在常驻内存方式下不容易被释放内存,小则内存泄漏,大则数据错乱。而沙盒可以很好的解决这个问题。(文章最后会介绍一个造成内存泄漏和数据错乱的案例)
$sandbox = $this->app->make(Sandbox::class);
请求进行预处理,这里进行的是request的转换,从swoole的request转换到tp的request
$request = $this->prepareRequest($req); $header = $req->header ?: []; $server = $req->server ?: []; if (isset($header['x-requested-with'])) { $server['HTTP_X_REQUESTED_WITH'] = $header['x-requested-with']; } if (isset($header['referer'])) { $server['http_referer'] = $header['referer']; } if (isset($header['host'])) { $server['http_host'] = $header['host']; } // 重新实例化请求对象 处理swoole请求数据 /** @var /think/Request $request */ $request = $this->app->make('request', [], true); return $request->withHeader($header) ->withServer($server) ->withGet($req->get ?: []) ->withPost($req->post ?: []) ->withCookie($req->cookie ?: []) ->withInput($req->rawContent()) ->withFiles($req->files ?: []) ->setBaseUrl($req->server['request_uri']) ->setUrl($req->server['request_uri'] . (!empty($req->server['query_string']) ? '&' . $req->server['query_string'] : '')) ->setPathinfo(ltrim($req->server['path_info'], '/'));
对沙盒进行设置,并初始化沙盒
$sandbox->setRequest($request); $sandbox->init();
启动沙盒
$response = $sandbox->run($request);
如果发生异常,则将异常信息处理并发送
try { $exceptionResponse = $this->app ->make(Handle::class) ->render($request, $e); $this->sendResponse($sandbox, $exceptionResponse, $res); } catch (Throwable $e) { $this->logServerError($e); }
最终需要将沙盒信息清除
$sandbox->clear();
以上是tp-swoole对HTTP的处理流程,下文会详细介绍沙盒的运行机制
class A{ private static $intance=null; public static function getInstance(){ if (!empty(self::$intance)){ return self::$intance; } self::$intance = new static(); return self::$intance; } public static function clear(){ self::$intance=null; } public function echo(){ echo "echo"; } } $b = A::getInstance(); A::clear(); print_r($b->echo());
以上代码会报错吗?
不会。仍然会输出echo
下面在做另外一个实验
class A { private static $intance = null; private $echo = 'echo'; public static function getInstance() { if (!empty(self::$intance)) { return self::$intance; } self::$intance = new static(); return self::$intance; } public static function clear() { self::$intance = null; } public function echo() { echo $this->echo; } public function setEcho($echo) { $this->echo = $echo; } } $b = A::getInstance(); $a = A::getInstance(); $a->setEcho("b"); print_r($b->echo()); A::clear(); print_r($b->echo()); $a->setEcho("a"); print_r($b->echo());
以上代码会输出什么?
答案是:bba。那么为什么不仅没有报错,还输出这样的答案
PHP的变量对象引入是地址引用,当$a和$b被赋值时,他们所存的内容都是一样的,且只有一份都是self::$intance位置所存放的内容。修改$a或$b都会修改self::$intance,那么为何当self::$intance为清除后,$a和$b仍然正常?基于PHP写时复制,当self::$intance被清空,就会复制出来一份给$a和$b来使用。
当我们在非常住内存方式开发时,这些都不需要注意,因为每次请求都相当于一个单独的线程,初始化所有数据,最后在将所有数据销毁,且所有数据都是按照顺序执行的。长住内存方式,就需要注意这些问题,不然会出现类似线程安全的问题。至于为何会出现这样的问题,下文再叙。