前言
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 来使用。
当我们在非常住内存方式开发时,这些都不需要注意,因为每次请求都相当于一个单独的线程,初始化所有数据,最后在将所有数据销毁,且所有数据都是按照顺序执行的。长住内存方式,就需要注意这些问题,不然会出现类似线程安全的问题。至于为何会出现这样的问题,下文再叙。