1.在application目录下边创建server目录用来存放启动http_server服务的类文件 然后创建http_server.php
上代码(好好看里面的注释):
<?php
/**
* Created by PhpStorm.
* User: dell
* Date: 2019/3/20
* Time: 10:36
*/
/*
* 这只一个http sever的基本服务 至少启动一个http服务 在linux终端执行该文件启动服务即可!
* thinkphp5.0有自己的swoole类库已经封装好了 http://packagist.p2hp.com/packages/topthink/think-swoole composer安装即可!
*
* */
$http = new swoole_http_server('0.0.0.0',9501);
$http->set(
[
'enable_static_handler'=>true,
'document_root'=>'/media/sf_ubt/tprbac/public/static/live',
'worker_num'=>5 //启动5个worker进程
]
);
/*
* 手册定义:此事件在Worker进程/Task进程启动时发生。这里创建的对象可以在进程生命周期内使用
* 通俗来讲:在启动http服务的时候先走到这里
* $server其实就是上边new出来的$http $worker_id woker进程id
* 上边我们设置的worker_num=5 那么表示起了5个worker进程 那么我们会将tp用到的一些东西热加载到每个worker进程当中 方便使用!
* */
$http->on('WorkerStart',function(swoole_server $server,$worker_id){
// 定义应用目录 注意路径
define('APP_PATH', __DIR__ . '/../application/');
/*
* ThinkPHP 引导文件
* 1. 加载基础文件 tp当中的index当中 thinkphp/start.php 然后再start.php当中引入了base.php 并且去执行了App::run()->send();如果在这里直接引入start.php那么程序就直接运行了 根本不走下边的OnRequest()回调!这样
* 使用swoole也就毫无意义了!所以这里只需引入base.php即可! App::run()->send();我们再OnRequest()回调当中引入即可!
* 2. 注意路径
* 3.当开启http_server服务的时候swoole就已经将thinkphp用到的所有涉及到的类库变量常量啥的都加载到了work进程当中 相当于常驻内存了!再过来请求的时候就会像以前的tp框架每次都加载一遍tp用到的各种类库常量变量等资源了!
* */
require __DIR__ . '/../thinkphp/base.php';
});
//只要我们开启了swoole当中的http服务那么所有的访问都会走到这里面来!
$http->on('request',function($request,$response) use($http){
/*
* 在这里我们需要对server header post get请求做一个转换
* 因为swoole当中的这些东西和原生的php当中是不一样的 但是tp5的这些获取post get header server cookie参数又是基于php原生的 所以我们需要做一个转换
* */
//这里之所以要清空$_SERVER是因为在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是因为在swoole当中如果进程不结束那么$_GET是不会被释放的 所以我们手动清空它
$_GET = [];
if(isset($request->get)){
foreach($request->get as $k=>$v){
$_GET[$k] = $v;
}
}
//这里之所以要清空$_POST是因为在swoole当中如果进程不结束那么$_POST是不会被释放的 所以我们手动清空它
$_POST = [];
if(isset($request->post)){
foreach($request->post as $k=>$v){
$_POST[$k] = $v;
}
}
ob_start();//开启缓存区
try{
// 执行应用 这里就是上边WorkerStart当中讲到的 在这里真正的去执行了thinkphp5.0当中的代码
think\App::run()->send();
}catch(\Exception $e){
//处理错误发生后的业务逻辑
}
$res = ob_get_contents();//获取缓冲区内容
ob_end_clean();//清空缓冲区
$response->end($res);//发送到客户端
});
$http->start();
?>
上边写到的是面向过程的代码 我们封装成面向对象的类文件:
<?php
/*
* 解释:这个是websocket服务
* 在swoole当中swoole_websocket_server是基于swoole_http_server的 swoole_http_server又是基于tcp服务的
* 所以我们在这里写的这个swoole_websocket_server即可供http访问 也可以供websocket服务访问
* 不同的是websocket服务需要有 open 和 message 两个方法的存在
* 也就是说当我们通过http形式访问 http://192.168.1.174:9510/index/index/aa 的时候走的是http服务
* 如果是通过 ws://192.168.1.174:9510/index/index/aa的时候那么自动走的就是websocket服务
* */
class Ws{
CONST HOST = "0.0.0.0";
CONST PORT = 9510;
public $ws = null;
public function __construct(){
$this->ws = new swoole_websocket_server(self::HOST,self::PORT);
$this->ws->set([
'enable_static_handler'=>true,
'document_root'=>'/media/sf_ubt/tprbac/public/static',
'worker_num'=>5,
'task_worker_num'=>4,
]);
// open 和 message 方法是在走websocket服务的时候才会用到的
$this->ws->on('open',[$this,'onOpen']);
$this->ws->on('message',[$this,'onMessage']);
$this->ws->on('workerstart',[$this,'onWorkerStart']);
$this->ws->on('request',[$this,'onRequest']);
$this->ws->on('task',[$this,'onTask']);
$this->ws->on('finish',[$this,'onFinish']);
$this->ws->on('close',[$this,'onClose']);
$this->ws->start();
}
public function onOpen($ws,$request){
// var_dump($request->fd);
}
public function onMessage($ws,$frame){
// echo "收到客户端消息是:{$frame->data}\n";
$ws->push($frame->fd,'服务器推送回去的消息是:woaini');
}
public function onWorkerStart($server,$worker_id){
//定义应用目录
define('APP_PATH', __DIR__ . '/../application/');
// 加载框架引导文件
require __DIR__ . '/../thinkphp/base.php';
}
public function onRequest($request,$response){
if(isset($request->header)){
foreach($request->header as $k=>$v){
$_SERVER[strtoupper($k)] = $v;
}
}
$_SERVER = [];
if(isset($request->server)){
foreach($request->server as $k=>$v){
$_SERVER[strtoupper($k)] = $v;
}
}
//这里之所以要清空$_GET是因为在swoole当中如果进程不结束那么$_GET是不会被释放的 所以我们手动清空它
$_GET = [];
if(isset($request->get)){
foreach($request->get as $k=>$v){
$_GET[$k] = $v;
}
}
$_GET['http_server'] = $this->ws;
//这里之所以要清空$_POST是因为在swoole当中如果进程不结束那么$_GET是不会被释放的 所以我们手动清空它
$_POST = [];
if(isset($request->post)){
foreach($request->post as $k=>$v){
$_POST[$k] = $v;
}
}
//这里我们将http_server放到$_POST当中 因为swoole是常驻内存的 也就是说设置完成必须重启http_server服务 然后常驻内存!
$_POST['http_server'] = $this->ws;
//需要cookie还可以写上cookie
//swoole当中目前是不支持session的
//需要files可以加上files 用于上传文件
$_FILES = [];
if(isset($request->files)){
foreach($request->files as $k=>$v){
$_FILES[$k] = $v;
}
}
ob_start();//开启缓存区
try{
// 执行应用 这里就是上边WorkerStart当中讲到的 在这里真正的去执行了thinkphp5.0当中的代码
think\App::run()->send();
}catch(\Exception $e){
//处理错误发生后的业务逻辑
echo $e->getMessage();
}
$res = ob_get_contents();//获取缓冲区内容
ob_end_clean();//清空缓冲区
$response->end($res);//发送到客户端
// $this->ws->close();//在开发阶段我们占时打开 模仿的根fpm一样
}
public function onTask($serv,$taskId,$workerId,$data){
//在这里打造一个工厂模式
$obj = new app\swoole\controller\factory\TaskFactory();
$method = $data['method'];
$st = $obj->$method($data);
return $st;
}
public function onFinish($serv,$taskId,$data){
// echo "taskId:{$taskId}\n";
// echo "finish-data-success:{$data}\n";
}
public function onClose($ws,$fd){
// echo "client:{$fd}\n";
}
}
new Ws();
?>
2 ThinkPHP 核心文件修改 改成下边的这样就行 为什么?因为thinkphp会记录每次的请求的模块/控制器/方法 到一个变量里面
我们的swoole加载的tp的资源放到了5个worker进程当中 它是常驻内存的
请求结束了也不会在内存当中释放!所以第一次请求/index/queue/aa 然后第二次请求是 /index/quuee/bb
但是还是会给你返回第一次的结果 这就不行啊! 所以修改了request类
thinkphp/library/think/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[Config::get('var_pathinfo')])) {
// 判断URL里面是否有兼容模式参数
$_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')];
unset($_GET[Config::get('var_pathinfo')]);
} elseif (IS_CLI) {
// CLI模式下 index.php module/controller/action/params/...
$_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
}
// 分析PATHINFO信息
if (!isset($_SERVER['PATH_INFO'])) {
foreach (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 = 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;
}
3.关闭debug以及页面trace 必须要关闭哦! 不然会报错的!
4.在Ws类中 onClose方法里面不要有输出 输出了也是打印到了后台服务端没意义 关键是会报错: Cannot modify header information - headers already sent by 原因就是你在onClose方法里面输出了 以为swoole加载了tp框架是常驻内存的 当第二次去访问的时候上一次的关闭了onClose方法执行 打印了 那么第二次去跑的时候我们在代码里面有$request->header 在header之前不能有任何的输出 所以就会上边的错误信息出来!!!
public function onClose($ws,$fd){
// echo "client:{$fd}\n";
}
swoole完美适配thinkphp5.0.22之后
在静态页面当中的根目录其实就是我们在http_websocket_server.php当中配置的静态资源目录就是根目录
在控制器当中的根目录其实就是http_websocket_server.php所在的目录就是根目录