ThinkPHP 5.1——使用 migrate 迁移和填充数据库 tp
说明:tp5.1最新版本是v2.0.3
《Thinkphp5入门系列课程》第十一课:Migration(一)
tp结合swoole的结合
- 1、tp不再使用Apache或者nginx作为服务器,而是swoole的http作为服务器。(后期,可以使用nginx的反向代理,使得nginx作为服务器)
- 2、在项目根目录,建立一个server文件夹,里面是swoole的http服务器代码,通过访问swoole服务器,来访问tp框架。
- 3、观察tp的入口文件,主要做两件事,一是,加载初始化tp的基本代码;而是进行网络的监听。所以,融合的思路是,在swoole启动的时候(监听workerStart)加载tp的基本框架给每个worker,访问swoole的时候(监听request)才调用tp的网络请求
一下是tp结合swoole的启动脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
<?php class WS{ private $host = '0.0.0.0'; private $port = '8888'; private $ws = null; /** 构造 * WS constructor. */ public function __construct() { // 创建 swoole websocket服务器实例 $this->ws = new swoole_websocket_server($this->host,$this->port); //websocket 服务器的初始化 $this->init(); } private function init() { //参数设置 $this->ws->set([ 'worker_num'=>5,//worker进程数 'task_worker_num'=>5,//task 任务进程数 'enable_static_handler'=>true,//开启静态文件处理 'document_root'=>'/var/wwwroot/tp51/public/static'//静态文件位置 ]); /*以下是swoole的生命周期的监听*/ //worker进程启动的时候 $this->ws->on('workerStart',[$this,'onWorkerStart']); //监听http请求 $this->ws->on('request',[$this,'onRequest']); //监听websocket打开的时候 $this->ws->on('open',[$this,'onOpen']); //监听websocket接收到信息的时候 $this->ws->on('message',[$this,'onMessage']); //监听任务投递 $this->ws->on('task',[$this,'onTask']); //监听任务完成后 $this->ws->on('finish',[$this,'onFinish']); //监听链接关闭后 $this->ws->on('close',[$this,'onClose']); //启动服务 $this->ws->start(); } /** 监听worker进程启动的时候 * @param $server swoole服务器对象 * @param $worker_id worker进程的id */ public function onWorkerStart($server,$worker_id) { /*在worker进程启动的时候,把TP框架的基础文件加载到每个worker进程里*/ define('APP_PATH',__DIR__.'/../application/'); // 加载基础文件 require __DIR__ . '/../thinkphp/base.php'; } /** 监听http的请求 * @param $request 请求对象 * @param $response 响应对象 */ public function onRequest($request,$response) { /* 因为http的请求只能swoole接收, PHP的全局变量接收不到值, 需要手动把swoole接收到的赋值给全局变量*/ $_SERVER=[]; $_GET=[]; $_POST=[]; 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; } } if (isset($request->get)){ foreach ($request->get as $k=>$v) { $_GET[$k] = $v; } } if (isset($request->post)){ foreach ($request->post as $k=>$v) { $_POST[$k] = $v; } } /*因为TP输出的内容在swoole里是不能直接输出客户端, 需要使用缓存接收TP的输出, 再由swoole统一输出到客户端*/ ob_start(); try{ // 每次请求才去调用TP \think\Container::get('app',[APP_PATH])->run()->send(); }catch (Exception $e){ $response->end($e->getMessage()); } $res = ob_get_contents(); ob_clean(); //设置响应头 $response->header('Content-Type', 'text/html; charset=UTF-8'); //响应输出,swoole的http只能通过end()方法向客户端输出 $response->end($res); } /** 监听websocket打开的时候 * @param $ws swoole服务实例 * @param $request 请求实例 */ public function onOpen($ws,$request) { echo "客户端-{$request->fd}-已链接" . PHP_EOL; } /**监听websocket接收到信息的时候 * @param $ws swoole服务实例 * @param $request 请求实例 */ public function onMessage($ws,$request) { echo "接收数据来自-{$request->fd}的数据-{$request->data},opcode-{$request->opcode},fin-{$request->finish}" . PHP_EOL; } /** 监听任务投递 * @param $ws swoole服务实例 * @param $taskId task任务id * @param $workerId worker进程id * @param $data 任务 * @return mixed 任务挖完成了必须要返回 */ public function onTask($ws, $taskId, $workerId, $data) { echo "处理的任务数据:" . PHP_EOL; print_r($data); sleep(10); return 'task finish'; } /** 监听任务完成后 * @param $ws swoole服务实例 * @param $taskId task任务id * @param $data 任务 */ public function onFinish($ws, $taskId, $data) { echo "taskId-{$taskId} 任务结束" . PHP_EOL; } /**监听链接关闭后 * @param $ws swoole服务实例 * @param $fd 链接句柄 */ public function onClose($ws,$fd) { echo "client: $fd is close".PHP_EOL; } } new WS(); |
使用PHP cli命令运行即可:php ws.php
这样一个swoole的服务就被启动,浏览器访问你设定好的ip与端口就可以了。
在访问swoole监听request的时候,需要注意问题:
- 1、请求参数一般都是PHP的全局变量接收的,比如$_SERVER,$_GET,$_POST等。但是这些参数都被swoole接收了,PHP的全局变量是没有任何值的,而且,tp框架的网络参数处理是依赖于这些全局变量的,因此需要把swoole接收的参数赋值个PHP的全局变量;
- 2、访问tp框架的具体方法的时候,结果是不会输出到浏览器的,只会输出到服务器上,因为在swoole输出到浏览器只有一个方法:$response->end();所以,需要收集tp的输出,通过swoole输出到浏览器上。
- 3、传参的时候,如果变换了参数名字,会发现之前的参数并没有消失,这是因为上面说到的swoole接收到的参数,会存在PHP的全局变量(数组)中,每次swoole赋值的时候,不会自动清空,导致新旧数据都有,所以,在接收参数之前,需要做判断,如果全局变量有值就清空,然后才赋值。
- 4、访问不同的方法的时候,没反应,老是输出index/index/index的内容。这是因为swoole是常驻内存的,可以在每次请求后把swoole服务关闭,导致每次请求都创建worker进程重新加载tp,这是一个办法,但是并不好,会发现终端swoole报错,告诉你这样的操作,swoole是不允许的。这里会有疑问:问什么swoole报错,还可以继续运行,浏览器能看见内容?这就涉及到swoole的工作机制,swoole报错了,是某个worker报错,会迅速拉起另外一个worker继续服务。回到正题,那么该如何做呢?这得从tp框架入手了。\think\Container::get(‘app’,[APP_PATH])->run()->send();这是tp框架请求处理的开始,更改tp的控制器加载策略,run()方法在\thinkphp\library\think\App.php里面,通过阅读run()发现了获取应用调度信息$path = $this->request->path();,接着找到path()方法,发现这就是需要修改的方法,把最外面判断去掉就可以,调用的$pathinfo = $this->pathinfo();方法也要把最外面的判断去掉。重启swoole,访问方式使用:host?s=模块/控制器/方法,的方式访问即可,如:http://tp51.net:8888/?s=index/index/index。
关于task任务的使用
- 调用第三方接口交给swoole的task去做,因为第三方接口可能不稳定,请求也是要耗费时间的,所以这些不确定,耗时长的任务交给task最合适了。
- task是必须要掌握的,否则swoole白学了。
- 因为swoole的task监听只用一个,而任务有很多,如果不做处理,所有的代码都挤在一起了,代码不好维护,所以,可以借助工厂模式等设计模式来管理task任务。
如何全局使用swoole?
只要把swoole的实例放到PHP的全局变量就可以了。
swoole也有redis方法
直播的业务流程
直播的数据库设计
swoole 的 websocket是建立在http之上的,所以在需要websocket的情况下不需要另外开http,只需要开websocket,也可以使用http的方法的
如何处理重复的图片
redis类库的优化,使用__call()实现用最少的代码实现更多的功能
中大型公司后台管理系统和客户端系统是分开的,后台放到公司内网的,只用内网才可以访问,客户端放到公网,全世界都可以访问
通过task推送消息给客户端
聊天室的实现
聊天室使用websocket
多端口监听
一份swoole里已经开了一个端口,那么现在需要开一个端口用于聊天室服务呢?这个swoole有解决方法,通过listen()方法完成多端口的监听。使用如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
class WS{ private $host = '0.0.0.0'; private $port = '8888'; private $port_chart = '8889';//新的端口 private $ws = null; public function __construct() { // 创建 swoole websocket服务器实例 $this->ws = new swoole_websocket_server($this->host,$this->port); // 多端口监听 $this->ws->listen($this->host,$this->port_chart,SWOOLE_SOCK_TCP); $this->init(); } private function init() { $this->ws->set([ 'worker_num'=>5,//worker进程数 'task_worker_num'=>5,//task 任务进程数 'enable_static_handler'=>true,//开启静态文件处理 'document_root'=>'/var/wwwroot/tp51/public/static'//静态文件位置 ]); $this->ws->on('workerStart',[$this,'onWorkerStart']); $this->ws->on('request',[$this,'onRequest']); $this->ws->on('open',[$this,'onOpen']); $this->ws->on('message',[$this,'onMessage']); $this->ws->on('task',[$this,'onTask']); $this->ws->on('finish',[$this,'onFinish']); $this->ws->on('close',[$this,'onClose']); $this->ws->start(); } |
swoole的connections
一个端口链接了很多客户端,但是服务器想要广播信息给全部人,那么找到这些用户呢?这就要到了swoole提供的connections属性了。connections顾名思义,就是链接的意思,表示链接到这个端口所有客户端的fd集合。
$connections属性是一个迭代器对象,不是PHP数组,所以不能用var_dump或者数组下标来访问,只能通过foreach进行遍历操作。
1 2 3 4 5 6 |
foreach($server->connections as $fd) { $server->send($fd, "hello"); } echo "当前服务器共有 ".count($server->connections). " 个连接\n"; |
这样就可以对所有客户端发送信息了。
websocket消息推送
使用push($fd,$data)方法,进行推送。$fd是客户端句柄(也就是唯一标识号),$data需要发送的数据。
结合上面connections就可以广播信息了:
1 2 3 4 |
//$swoole_server是实例化后的swoole服务器 foreach ($swoole_server->connections as $fd){ $swoole_server->push($fd,$data); } |
公共聊天室的流程:
- 1、服务器开启websocket端口;(如:开了8888端口)
- 2、客户端使用websocket链接上服务器,链接地址一般就是服务器地址+端口号就好了;(如:mySocket = new WebSocket(“ws://localhost.com:8889”);)
- 4、客户端还需要websocket的onMessage()方法监听服务器推送的数据;
- 5、客户端发送的数据则走ajax,发送到对应的控制器的方法上,进行逻辑与数据的处理,这里客户端就不要使用websocket提供的send()方法发送数据了。
- 6、服务端在对应的控制器的方法接收数据,并且通过connections属性+push()方法来对链接到这个端口的所有客户端广播信息