# ThinkPHP 5.1 Swoole 快速上手指南 本篇内容主要讲述了最新的`think-swoole`扩展的使用。 [TOC=2,3] >[info] 本指南的目的不是为了让你掌握`Swoole`开发,而且帮助你使用`think-swoole`快速部署`ThinkPHP5.1`应用到`Swoole`的`HttpServer`,以及使用快速启动`Swoole`服务,如果你需要了解`Swoole`的具体用法和原理,请参考[Swoole官方文档](https://wiki.swoole.com/),说的比较详细了。 > >[danger]#### 本文的内容并不适用于ThinkPHP `5.0`及以下版本(`5.0`或者`5.1.18`之前版本的`Swoole`的支持可以[参考这里](https://segmentfault.com/a/1190000015001872)) ! ## 安装`Swoole` 首先按照[Swoole官网](http://www.swoole.com)说明安装`swoole`扩展,推荐新手可以直接使用 ~~~ sudo pecl install swoole ~~~ 会安装最新的稳定版(截至本文发布最新版本是`4.0.3`版本),如果你需要安装某个版本,例如,如果你不需要使用协程功能,只需要安装`1.*`版本,可以使用: ~~~ sudo pecl install swoole-1.10.5 ~~~ 可以在[这里](https://pecl.php.net/package/swoole)查看所有的swoole版本。 安装完成后,你可能需要在你的`php.ini`中添加: ~~~ extension=swoole ~~~ 最后,请确认你的php的`swoole`模块已经支持。 ~~~cmd php -m ~~~ 如果你能够看到`swoole`在列表中,说明`swoole`模块已经正常安装了。 如果安装过程中遇到问题,根据提示进行操作即可,或者自行百度,这里不再赘述。 ## 安装`think-swoole` 接下来第二步是安装`think-swoole`扩展,本文中的内容以最新版本的扩展为例(可能部分功能老版本的扩展不支持),如果你的扩展版本较旧,请更新框架或者扩展版本。 > `think-swoole`是ThinkPHP官方发布的`swoole`扩展,从`2.0+`版本完善了对`Swoole`的支持。 `ThinkPHP5+`的扩展都是基于`Composer`安装的,所以确认你已经安装了`Composer`。 如果你已经有自己的ThinkPHP`5.1`项目了,为了使用最新的特性,**建议更新到最新版本**(`V5.1.20+`),然后可以在应用根目录下使用下面命令安装扩展。 ~~~ composer require topthink/think-swoole ~~~ 会安装最新的稳定版本的`think-swoole`扩展。 如果你是第一次使用ThinkPHP`5.1`,那么可以先创建一个初始项目,然后再安装扩展,依次执行下面的命令即可。 ~~~ composer create-project topthink/think tp cd tp composer require topthink/think-swoole ~~~ ## 启动`Swoole HTTP`服务 第一个场景(也是该扩展最重要的一个场景),毕竟大部分使用`think-swoole`扩展的用户都是在使用ThinkPHP开发网站或者项目,使用`think-swoole`扩展可以让你的产品直接部署到`Swoole`上,并且享受下面的优势: * 无需对代码进行改造就能带来性能的数倍提升; * 可以在`Apache`/`Nginx`等传统WEB服务器和`Swoole`之间切换部署; > 简单点说,就是你可以在传统模式下开发你的应用,然后直接部署到`Swoole`上运行,但无需针对`Swoole`写任何的处理代码。 安装完扩展后,你什么都不需要做,最简单的就是直接在命令行(应用根目录下面)下执行: ~~~cmd php think swoole ~~~ 启动成功后会显示 ~~~ Starting swoole http server... Swoole http server started: <http://0.0.0.0:9501> You can exit with `CTRL-C` ~~~ 可以看到已经在`0.0.0.0:9501`启动一个HTTP Server服务端,下面我们可以直接访问当前的应用。 ~~~ http://localhost:9501 ~~~ 如果你之前已经有运行一个80端口的WEB服务,那么可以比较下两个页面的区别。 如果你是刚创建的项目,那么可以直接看到ThinkPHP`5.1`的欢迎页面。 ![](https://box.kancloud.cn/c9a40a11d4db937ee90998c8f92b22ea_763x286.png) 否则你会看到你的项目首页。 ### 配置文件 `HTTPServer`的参数可以在应用配置目录下的`swoole.php`里面配置,该文件会在扩展安装的时候自动生成(如果没有则可以自己创建)。 扩展自带的配置参数主要包括: 配置参数 | 描述|默认值 --- | --- | --- host | 监听地址|0.0.0.0 port | 监听端口|9501 mode | 运行模式|SWOOLE_PROCESS sock_type | Socket type|SWOOLE_SOCK_TCP app_path | 应用目录(守护进程模式必须设置)|自动识别 ssl | 是否启用https|false file_monitor | 是否监控文件更改(V2.0.9+)| false file_monitor_interval| 监控文件间隔(秒)(V2.0.9+)| 2 file_monitor_path | 监控目录 (V2.0.9+)| 默认监控application和config目录 > 其它的`swoole`参数可以参考官方文档的[配置参数](https://wiki.swoole.com/wiki/page/274.html),所有`swoole`本身支持的配置参数都可以直接在`swoole.php`中使用。 ### 守护进程模式 如果需要使用守护进程模式运行,可以使用 ~~~cmd php think swoole -d ~~~ 或者在`swoole.php`文件中设置 ~~~ 'daemonize' => true ~~~ 不过一定要记得,如果启用守护进程模式,必须设置应用目录`app_path`(使用绝对路径),否则会出错。 ~~~ 'host' => '0.0.0.0', // 监听地址 'port' => 9501, // 监听端口 'daemonize' => true, 'app_path' => '/home/www/tp/application/', ~~~ ### 基本操作 如果要停止服务,可以使用 ~~~cmd php think swoole stop ~~~ `reload`服务 ~~~cmd php think swoole reload ~~~ `stop`服务 ~~~cmd php think swoole stop ~~~ `restart`服务 ~~~cmd php think swoole restart ~~~ > `restart`和`reload`的区别是,`restart`会先`stop`然后`start`,而`reload`则是平滑重启服务,不会中断服务。 如果你需要修改地址和端口,可以修改`swoole.php`配置文件 ~~~ 'host' => 'tp5.com', // 监听地址 'port' => 8080, // 监听端口 ~~~ 改完后,需要重启服务才能生效 ~~~cmd php think swoole restart ~~~ 现在可以直接访问 ~~~ http://tp5.com:8080 ~~~ >[danger] 如果你需要设置`80`端口,需要`root`权限才可以。 > 如果你安装的是`2.0.12+`版本的扩展,还可以支持在命令行指定地址和端口,例如: ~~~cmd php think swoole -H tp.com -p 9508 ~~~ 如果启动了多个不同端口的服务,`reload`、`restart`和`stop`操作必须也是针对某个端口的才能正确操作,我们以`reload`操作为例进行说明。 如果我们需要`reload`前面启动的`tp.com:9508`服务,下面的指令是错误的 ~~~cmd php think swoole reload ~~~ 可能会出现错误提示: ~~~ no swoole http server process running. ~~~ 必须带上正确的端口号(`host`不是必须的) ~~~cmd php think swoole reload -p 9508 ~~~ 然后,你会看到提示信息如下,表示`reload`成功: ~~~ Reloading swoole http server... > success ~~~ ### `Cookie`和`Session` 由于`Swoole`的特殊性,扩展本身接管了`Cookie`类和`Session`类的处理,因此不要调用(包括依赖注入)`think\Cookie`和`think\Session`类,而应该改为`think\swoole\Cookie`类和`think\swoole\Session`类。但`think\facade\Cookie`和`think\facade\Session`类的用法是正常的,因此原来的静态方法调用依然可以正常使用。同时,`Request`对象的`cookie`方法和`session`方法也可以正常使用。 关于`Swoole`的`Session`的用法,这里要特别强调下。 >[danger] `Swoole`是没有`Session`的概念,因此所有PHP内置的`session`函数都是无效的,`think-swoole`扩展单独封装了一个`Session`类,和系统的`Session`机制无关。 该扩展提供的`think\swoole\Session`类是基于`swoole_table`和`ThinkPHP`缓存的混合解决方案。 每次`Session::start()`的时候系统会从`swoole_table`或者定义的缓存类型中获取当前用户的`Session`数据,而`session_id`数据则通过`Cookie`获取。并且在当前`worker`进程中不会过期,但每次从`swoole_table`或者缓存中获取`session`的时候则会判断是否过期,`session`的有效期还是通过`session.php`配置文件的`expire`配置参数进行设置。 > `swoole_table`一个基于共享内存和锁实现的超高性能,并发数据结构。用于解决多进程/多线程数据共享和同步加锁问题。 由于`swoole_table`需要事先分配内存大小和字段定义,在`swoole.php`配置文件中需要添加定义: ~~~ 'table' => [ // 定义最大记录数 'size' => 1024, // 字段类型定义(目前仅支持 string int 和 float类型) 'column' =>[ 'data' => ['string',255], // 字符串类型 长度为255个字节 'expire'=> ['int',8], // 整型 长度为8 ], ], ~~~ > `swoole_table`分配的内存无法动态扩容和调整字段类型,如果修改则需要重启才能生效。 然后在`session.php`配置文件中,添加 ~~~ 'use_swoole_table' => true, ~~~ `swoole_table`是一个可选方案。我们更建议使用缓存机制来处理`Session`,如果你的应用比较大,则应该配置使用`redis`之类的缓存机制更加适合,直接在你的`cache.php`中定义相关缓存配置即可。 >[danger] 为了避免复杂,swoole的`Session`类不再支持`prefix`参数,如果需要区分比如前后台不同`session`的需求,可以使用`name`参数进行区分。 ### 文件监控 由于`Swoole`服务运行过程中PHP文件是常驻内存运行的,这样可以避免重复读取磁盘、重复解释编译PHP,以便达到最高性能。所以更改业务代码后必须手动`reload`或者`restart`才能生效。 `think-swoole`扩展提供了监控文件更新的功能,在检测到相关目录的文件有更新后会自动`reload`,从而不需要手动进行`reload`操作,方便开发调试。 如果你的应用开启了调试模式,文件监控功能是自动开启的。原则上,在部署模式下不建议开启文件监控,一方面有性能损耗,另外一方面对文件所做的任何修改都需要确认无误才能进行更新部署。如果你确实需要在部署模式下开启文件监控,可以设置如下: ~~~ 'file_monitor' => true, // 开启文件监控 'file_monitor_interval' => 1, // 文件监控检测的时间间隔 'file_monitor_path' => '', // 文件监控目录 一般不需要设置 默认会监控应用目录和配置目录 ~~~ ### 事件回调 扩展自带的`HTTPServer`包含了`onWorkerStart`和`onRequest`两个事件回调,你如果需要增加其它的回调事件处理,可以在配置文件中直接添加: ~~~ 'WorkerStop' => function($server, $worker_id) { }, 'WorkerError' => function($serv, $worker_id, $worker_pid, $exit_code, $signal) { }, ~~~ 关于事件回调的具体用法,可以参考`swoole`官方文档。 >[danger] 如果不熟悉内部机制,请勿随意替换和更改`onWorkerStart`和`onRequest`事件回调,会导致不可预期的结果。 ### `Nginx+Swoole`部署 可以使用`Nginx`请求转发到`Swoole`的方式部署,充分发挥`Nginx`的配置优势和强大功能。 ~~~ server { listen 80; root /var/www/tp/public/; server_name 127.0.0.1; index index.html index.htm index.php; location / { proxy_http_version 1.1; proxy_set_header Connection "keep-alive"; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://127.0.0.1:9501; } } ~~~ ### 静态资源访问 如果你没有使用`Nginx`代理的话,为了确保静态资源的正常访问,请确认下面的参数配置正确: ~~~ // 网站根目录位置 'document_root' => Env::get('root_path') . 'public', // 开启静态资源处理 'enable_static_handler' => true, ~~~ > 使用Chrome浏览器会自动请求一次`favicon.ico`,所以确保你的网站根目录下面有存在`favicon.ico`文件,否则会产生一次`404`请求的错误日志。 ### `HTTPS`和`HTTP2`支持 如果需要配置你的`HTTP`服务支持`HTTPS`,需要增加配置如下: ~~~ 'ssl' => true, 'ssl_cert_file' => __DIR__.'/ssl.crt', 'ssl_key_file' => __DIR__.'/ssl.key', ~~~ 记得准确指定你的`cert`证书和`key`私钥的路径。 >[danger] 使用`SSL`必须在编译`swoole`时加入`--enable-openssl`选项 > 如果需要支持`HTTP2`协议,则在SSL支持的基础上还需要在编译的时候加入`--enable-http2`选项 ~~~ ./configure --enable-openssl --enable-http2 ~~~ 然后在`swoole.php`中增加配置 ~~~ 'open_http2_protocol' => true ~~~ ### 其它注意事项 > 为了让你的应用能够顺利的运行在`Swoole`上面,扩展做了大量的底层处理工作,包括让你的请求数据、`Cookie`和`Session`正常运作。 >[danger] 在`Swoole`下面,不能使用`$_GET`、`$_POST`、`$_REQUEST`、`$_SERVER`、`$_COOKIE`以及`$_SESSION`等原生的PHP用法,只能使用框架提供的类和方法进行获取。 错误的用法: ~~~ $name = $_GET['name']; $name = $_POST['name']; $name = $_COOKIE['name']; $name = $_SESSION['name']; $host = $_SERVER['HTTP_HOST']; ~~~ >[info] `V2.0.11+`版本开始,系统可以支持原生全局变量的获取,但仍然不建议使用。 > 正确的用法(以下用法都采用了`Facade`静态代理类): ~~~ $name = Request::get('name'); $name = Request::param('name'); $name = Cookie::get('name'); $name = Session::get('name'); $host = Request::server('http_host'); ~~~ 如果你要获取`php://input`内容,必须把原来的代码 ~~~ file_get_contents('php://input'); ~~~ 改成 ~~~ Request::getInput(); ~~~ 不过更建议使用 ~~~ Request::put(); ~~~ 因为可以支持`json`数据的自动解析而不需要手动进行`json_decode`。 由于`onWorkerStart`运行的时候还没有`HTTP_HOST`,因此最好在应用配置文件`config/app.php`中设置`app_host`。 请不要调用PHP原生的`header`方法,使用`Response`对象的`header`方法替代。 不要使用PHP原生的`session`相关函数,使用`Session`类的相关方法。 > 目前为止,尚有一些功能不够完善(例如文件上传之类),请期待后续版本更新。 ## 快速启动`Swoole Server` 现在来看第二个场景,通过简单的配置快速启动一个`swoole`服务,包括`WebSocket`/`Http`/`Socket`服务。 可以支持直接启动一个Swoole server(需要`think-swoole`扩展 `2.0.9+`版本) ~~~cmd php think swoole:server ~~~ 会显示如下信息: ~~~ Starting swoole server... Swoole socket server started: <0.0.0.0:9508> You can exit with `CTRL-C` ~~~ 这个时候已经在`0.0.0.0:9508`启动一个`Websocket`服务。 你可以在浏览器中访问 ~~~ http://127.0.0.1:9508 ~~~ 会看到类似下面的页面(后面是一串随机数)。 ![](https://box.kancloud.cn/218d7043017677c03124a79c92d567b0_377x86.png) ### 守护进程 如果需要使用守护进程方式运行,可以使用 ~~~cmd php think swoole:server -d ~~~ 或者在`swoole.php`文件中设置 ~~~ 'daemonize' => true ~~~ ### 配置文件 如果需要自定义参数,可以在`config/swoole_server.php`中进行配置,包括: 配置参数 | 描述|默认值 --- | --- | --- type| 服务类型(支持socket、http或者留空)| socket host | 监听地址|0.0.0.0 port | 监听端口|9508 mode | 运行模式|SWOOLE_PROCESS sock_type | Socket type|SWOOLE_SOCK_TCP daemonize|守护进程|false >[danger] 注意不要和`swoole.php`文件文件混淆,两者的作用完全不同。 并且支持`swoole`所有的参数,以及支持使用闭包方式定义相关事件回调。 ~~~ return [ // 扩展自身配置 'host' => '0.0.0.0', // 监听地址 'port' => 9501, // 监听端口 'type' => 'socket', // 服务类型 支持 socket http或者留空 'mode' => SWOOLE_PROCESS, 'sock_type' => SWOOLE_SOCK_TCP, // 可以支持swoole的所有配置参数 'daemonize' => false, 'worker_num' => 4, // 事件回调定义 'onOpen' => function ($server, $request) { echo "server: handshake success with fd{$request->fd}\n"; }, 'onMessage' => function ($server, $frame) { echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n"; $server->push($frame->fd, "this is server"); }, 'onRequest' => function ($request, $response) { $response->end("<h1>Hello Swoole. #" . rand(1000, 9999) . "</h1>"); }, 'onClose' => function ($ser, $fd) { echo "client {$fd} closed\n"; }, ]; ~~~ ### 自定义服务类 如果你需要更高级的自定义事件回调,也可以使用自定义的`Swoole`服务类。 ~~~ <?php namespace app\http; use think\swoole\Server; class Swoole extends Server { protected $host = '127.0.0.1'; protected $port = 9502; protected $serverType = 'socket'; protected $mode = SWOOLE_PROCESS; protected $sockType = SWOOLE_SOCK_TCP; protected $option = [ 'worker_num'=> 4, 'daemonize' => true, 'backlog' => 128 ]; public function onReceive($server, $fd, $from_id, $data) { $server->send($fd, 'Swoole: '.$data); } } ~~~ >[danger] 自定义服务类必须继承`think\swoole\Server`类,支持`swoole`所有的回调方法定义(回调方法必须是`public`类型)。 `serverType` 属性定义为 `socket`或者`http` 则支持swoole的`swoole_websocket_server`和`swoole_http_server` 然后在`swoole_server.php`中增加配置参数: ~~~ return [ 'swoole_class' => 'app\http\Swoole', ]; ~~~ > 定义该参数后,其它配置参数均不再有效。 然后就可以在命令行启动服务端 ~~~cmd php think swoole:server ~~~ 一样可以支持使用守护进程模式运行, ~~~cmd php think swoole:server -d ~~~ 同样也支持`reload`、`restart`和`stop` 操作。 ~~~cmd php think swoole:server reload ~~~ 客户端代码的实现有很多,如果你是使用PHP的话,可以用`Swoole\Client`类。 ~~~ <?php namespace app\index\controller; use Swoole\Client; use think\Controller; class Test extends Controller { public function index() { $client = new Client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC); $ret = $client->connect("127.0.0.1", 9501); if(empty($ret)){ echo 'error!connect to swoole_server failed'; } else { $client->send('test'); } } } ~~~ ### 启动多个`swoole`服务 你可以通过命令行的指令启动多个不同端口的`swoole`服务,例如: ~~~ php think swoole:server -p 9800 php think swoole:server -p 9700 ~~~ 如果要分别对不同端口的服务进行`stop`操作,务必使用 ~~~ php think swoole:server stop -p 9800 php think swoole:server stop -p 9700 ~~~