基于thinkphp5.1进行整合swoole。此篇文章涉及的知识点有点多。值得注意的是:这篇文章不是单单整合swoole服务端,还包括模型整合,redis协程开发等。
基础搭建
基于thinkphp5.1开启socket服务,我这里使用了自定义服务类
在config文件夹配置里面加入PHP配置代码
<?php// +----------------------------------------------------------------------// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]// +----------------------------------------------------------------------// | Copyright (c) 2006-2018 http://thinkphp.cn All rights reserved.// +----------------------------------------------------------------------// | Licensed ( PHP开发/https://aiznh.com/wp-content/uploads/2021/06/20210608192146-60bfc34a4a4b9.txt )// +----------------------------------------------------------------------// | Author: liu21st <liu21st@gmail.com>// +----------------------------------------------------------------------use think\\facade\\Env;// +----------------------------------------------------------------------// | Swoole设置 php think swoole:server 命令行下有效// +----------------------------------------------------------------------return [\'swoole_class\' => \'app\\server\\Swoole\', // 自定义服务类名称];
Swoole的自定义类
<?phpnamespace app\\server;use think\\swoole\\Server;use \\app\\dati\\logic\\Player;use think\\facade\\Cache;class Swoole extends Server{use \\app\\dati\\logic\\Common;protected $host = \'0.0.0.0\';protected $port = 9504;protected $serverType = \'socket\'; //服务类型protected $option = [\'worker_num\' => 1, //处理进程数\'backlog\' => 128,\'task_worker_num\' =>1, //开启任务数量\'mode\' => SWOOLE_PROCESS,\'socket_type\' => SWOOLE_SOCK_TCP,\'daemonize\' => false,//可以设置 dispatch_mode=5 设置以此值进行 hash 固定分配。可以保证某一个 UID 的连接全部会分配到同一个 Worker 进程\'dispatch_mode\' =>5,\'hook_flags\' => SWOOLE_HOOK_ALL, //swoole一键协程\'task_enable_coroutine\'=>true, //任务协程];/*** $frame->fd 客户端的 socket id,使用 $server->push 推送数据时需要用到* $frame->data 数据内容,可以是文本内容也可以是二进制数据,可以通过 opcode 的值来判断* $frame->opcode WebSocket 的 OpCode 类型,可以参考 WebSocket 协议标准文档* $frame->finish 表示数据帧是否完整,一个 WebSocket 请求可能会分成多个数据帧进行发送(底层已经实现了自动合并数据帧,现在不用担心接收到的数据帧不完整)* 参考文档* PHP开发/ */public function onMessage($ws, $frame) {}public function onClose($server,$fd){}public function onWorkerStart(){$_SERVER[\'redispool\'] = new \\Swoole\\Database\\RedisPool((new \\Swoole\\Database\\RedisConfig)->withHost(\'127.0.0.1\')->withPort(6379)->withAuth(\'\')->withDbIndex(0)->withTimeout(1),100);}public function onWorkerStop($server,$workerId){}/*** PHP开发/ 异步风格写法* *//*public function onTask($server, $task_id, $from_id, $data){go(function() use ($data){//$s=rand(1,10);sleep(5);//每个任务//$this->swoole->clearTimer($data);//$server->clearTimer($data);echo \'任务执行ID:\'.$data . \'\\n\';//co::sleep(0.2); //协程 API});}*///V4.2.12 起如果开启了 task_enable_coroutinepublic function onTask($server, $task){echo \"#\\ttask_id:{$task->id}\\tonTask: worker_id={$server->worker_id}\\n\";}}
使用上下文
在使用swoole的协程的时候,一不小心就会出现公用MYSQL连接的问题,解决这个问题,我们就结合使用上下文类进行处理,其实上下文主要解决的问题就是:在同一个协程内公用一个资源,与其他协程独立,互不影响,也互不干扰。不明白的可以看下这里的第九点
整合model
- 在开发的过程中,也经常遇到协程共用一个数据库资源链接导致出现的问题。主要核心的问题就是不能在同一个协程中使用同一个链接资源去执行数据库操作。当然其中很多种方法是能够解决这个问题的,如使用db,这个函数是可以能够重新链接一个数据库去执行操作。但是如果在一个程序使用很多个db去操作,可能很容易出现资源被占满和造成浪费。当然我也尝试过使用db::connect这个其实和db原理是一样的。当然使用上面的两种方式不能够兼容要model里面,这也不可以从根本上解决问题,所以下面就可以使用上下文操作类同一协程共用资源操作。
THINKPHP运行的方式是单例执行框架代码的,当我们使用协程去处理的话,需要考虑同一条连接mysql是可能产生并发的,他会提示一个有另外一个协程正在处理,不可同时使用,需要解决当前问题,只需要兼容一下数据库模型即可。
原理:重载连接数据库操作并放到上下文资源操作类即可。
我们先建立一个model的基类,做重载连接数据库操作即可
<?php/*** BaseModel.php* 兼容协程重载基类* @author : iqgmy* @date : 2017.5.28* @version : v1.0.0.0*/namespace app;use think\\Model;use think\\Db;use \\app\\server\\Context;class BaseModel extends Model{public $coroute = false;/*------------------------------------------------------ *///-- 优先自动执行/*------------------------------------------------------ */public function initialize(){parent::initialize();}/*** 创建模型的查询对象* @access protected* @return Query*/protected function buildQuery(){//兼容协程if(class_exists(\'Co\')&& \\Co::getcid() > 0){$key = get_class();$query = Context::get($key);if(is_null($query)){//重置并且需要重连数据库$query = Db::connect($this->connection, true, $this->query);Context::put($key,$query);}}else{// 设置当前模型 确保查询返回模型对象$query = Db::connect($this->connection, false, $this->query);}$query->model($this)->name($this->name)->json($this->json, $this->jsonAssoc)->setJsonFieldType($this->jsonType);if (isset(static::$readMaster[\'*\']) || isset(static::$readMaster[static::class])) {$query->master(true);}// 设置当前数据表和模型名if (!empty($this->table)) {$query->table($this->table);}if (!empty($this->pk)) {$query->pk($this->pk);}return $query;}}
在使用协程开发的时候,我们要时时刻刻保持警惕,我是否有共用一个连接一个数据操作,否则很容易出现has already been bound to another coroutine
整合redis
在整合swoole自定义的时候,开启worker就已经开启了一个全局的redis连接池,当然我也对其做了一个封装。原理和mysql上下文一起使用的
<?phpnamespace app\\server;use Swoole\\Coroutine;class CorRedis{public $connect ;protected $debug = false; //调试模式//实例对象public static function instance(){if($this->debug){$redis = new \\Redis(); //可以实现这个。这个暂时没有做调试}else{$redis = Context::get(\'corredis\');if(is_null($redis)){$redis = $_SERVER[\'redispool\']->get();if(is_null($redis)){throw new Exception(\'redis连接池为空\',4000);}Context::put(\'corredis\',$redis);}}return $redis;}public static function close(){//$redis = Context::get(\'corredis\');Context::delete(\'corredis\');}//最终返回一个对象public function __call($method, $args){// return call_user_func_array([$this->instance(),$method],$args);}//最终返回一个对象public static function __callStatic($method, $args){return call_user_func_array([self::instance(),$method],$args);}}
在使用上下文连接的时候,使用完之后必须要销毁掉,不然很容易造成资源浪费。
在学习的过程中,只有自己知道了原理,然后慢慢的摸索,我们就会懂得很多。