swoole完美适配thinkphp5.0.22框架

本文详细介绍了如何使用Swoole HTTP Server与ThinkPHP 5.0框架进行整合,包括代码实现、关键配置修改及常见问题解决。通过面向过程和面向对象两种方式展示Swoole服务启动流程,同时对ThinkPHP请求处理类进行调整以适应Swoole的特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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所在的目录就是根目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值