前言ThinkPHP即将迎来最新的6.0版本。鉴于Swoole越来越流行,thinkphp也推出了最新的扩展think-swoole3.0架构解析tp-swoole3。0与2.0版本不同,采用了全新的架构。(目录结构如下图)tp主要针对非驻留内存模式运行。为了兼容swoole,虽然做了很多优化,但是还是不能和swoft、sd等一些为swoole开发的框架一样。这里说的区别并不是说tp不好,而是因为两种模式必须要兼容,需要做一些取舍。请求分析框架的运行机制。其实分析swoole的OnRequest函数就够了。路由分发、数据处理等都在函数处处理。Swoole.phppublicfunctiononRequest($req,$res){$this->app->event->trigger('swoole.request');}$this->resetOnRequest();/**@varSandbox$sandbox*/$sandbox=$this->app->make(Sandbox::class);$request=$this->prepareRequest($req);尝试{$sandbox->setRequest($request);$沙盒->初始化();$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);}}最后{$sandbox->clear();}}在函数的开头,触发了一个request事件,方便用户自定义处理请求,进行一些自定义处理$this->app->event->trigger('swoole.request');resetrequest,当是Websocket时,reset这个类,具体为什么,下次分析Websocket时再说明$this->resetOnRequest();protectedfunctionresetOnRequest(){//重置websocket数据if($this->isServerWebsocket){$this->app->make(Websocket::class)->reset(true);接下来,通过容器获取沙箱。这也是非常驻内存框架中的关键点。为了方便,有一些写法在常驻内存模式下很难释放内存。小的是内存泄漏,大的是数据乱序。沙箱可以很好的解决这个问题。(文末会介绍一个内存泄露和数据混乱的案例)$sandbox=$this->app->make(Sandbox::class);请求预处理,这里是请求的转换,由swoole的request转换为tprequest$request=$this->prepareRequest($req);$header=$req->header?:[];$server=$req->server?:[];如果(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'];}//重新实例化Request对象处理swoole请求数据/**@var\think\Request$request*/$request=$this->app->make('request',[],true);返回$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['路径信息'],'/'));设置沙箱并初始化沙箱$sandbox->setRequest($request);$沙盒->初始化();启动沙箱$response=$sandbox->run($request);如果发生异常,则处理并发送异常信息$this->sendResponse($sandbox,$exceptionResponse,$res);}catch(Throwable$e){$this->logServerError($e);}最后需要清除沙箱信息$sandbox->clear();以上是tp-swoole的HTTP处理流程,下面将详细介绍沙箱的运行机制。容易忽略的常驻内存问题classA{privatestatic$intance=null;publicstaticfunctiongetInstance(){if(!empty(self::$intance)){returnself::$intance;}self::$intance=newstatic();返回自我::$实例;}publicstaticfunctionclear(){self::$intance=null;}publicfunctionecho(){echo"echo";}}$b=A::getInstance();A::clear();print_r($b->echo());上面的代码会报错吗?它仍然不会输出回声。我在做另一个实验classA{privatestatic$intance=null;私人$echo='echo';publicstaticfunctiongetInstance(){if(!empty(self::$intance)){returnself::$intance;}self::$intance=newstatic();返回自我::$实例;}publicstaticfunctionclear(){self::$intance=null;}publicfunctionecho(){echo$this->echo;}publicfunctionsetEcho($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还是正常的呢?基于PHPcopy-on-write,当self::$intance为空时,会复制一份给$a和$b使用。当我们在非驻留内存模式下开发时,这些不需要关注,因为每个请求相当于一个单独的线程,初始化所有数据,最后销毁所有数据,所有数据都是按顺序执行的。在长期内存模式下,需要注意这些问题,否则会出现类似线程安全的问题。至于为什么会出现这个问题,后面再说。
