# ThinkPHP 5.1 Swoole 快速上手指南
本篇內(nèi)容主要講述了最新的`think-swoole`擴展的使用。
[TOC=2,3]
>[info] 本指南的目的不是為了讓你掌握`Swoole`開發(fā),而且?guī)椭闶褂胉think-swoole`快速部署`ThinkPHP5.1`應(yīng)用到`Swoole`的`HttpServer`,以及使用快速啟動`Swoole`服務(wù),如果你需要了解`Swoole`的具體用法和原理,請參考[Swoole官方文檔](https://wiki.swoole.com/),說的比較詳細(xì)了。
>
>[danger]#### 本文的內(nèi)容并不適用于ThinkPHP `5.0`及以下版本(`5.0`或者`5.1.18`之前版本的`Swoole`的支持可以[參考這里](https://segmentfault.com/a/1190000015001872)) !
## 安裝`Swoole`
首先按照[Swoole官網(wǎng)](http://www.swoole.com)說明安裝`swoole`擴展,推薦新手可以直接使用
~~~
sudo pecl install swoole
~~~
會安裝最新的穩(wěn)定版(截至本文發(fā)布最新版本是`4.0.3`版本),如果你需要安裝某個版本,例如,如果你不需要使用協(xié)程功能,只需要安裝`1.*`版本,可以使用:
~~~
sudo pecl install swoole-1.10.5
~~~
可以在[這里](https://pecl.php.net/package/swoole)查看所有的swoole版本。
安裝完成后,你可能需要在你的`php.ini`中添加:
~~~
extension=swoole
~~~
最后,請確認(rèn)你的php的`swoole`模塊已經(jīng)支持。
~~~cmd
php -m
~~~
如果你能夠看到`swoole`在列表中,說明`swoole`模塊已經(jīng)正常安裝了。
如果安裝過程中遇到問題,根據(jù)提示進(jìn)行操作即可,或者自行百度,這里不再贅述。
## 安裝`think-swoole`
接下來第二步是安裝`think-swoole`擴展,本文中的內(nèi)容以最新版本的擴展為例(可能部分功能老版本的擴展不支持),如果你的擴展版本較舊,請更新框架或者擴展版本。
> `think-swoole`是ThinkPHP官方發(fā)布的`swoole`擴展,從`2.0+`版本完善了對`Swoole`的支持。
`ThinkPHP5+`的擴展都是基于`Composer`安裝的,所以確認(rèn)你已經(jīng)安裝了`Composer`。
如果你已經(jīng)有自己的ThinkPHP`5.1`項目了,為了使用最新的特性,**建議更新到最新版本**(`V5.1.20+`),然后可以在應(yīng)用根目錄下使用下面命令安裝擴展。
~~~
composer require topthink/think-swoole
~~~
會安裝最新的穩(wěn)定版本的`think-swoole`擴展。
如果你是第一次使用ThinkPHP`5.1`,那么可以先創(chuàng)建一個初始項目,然后再安裝擴展,依次執(zhí)行下面的命令即可。
~~~
composer create-project topthink/think tp
cd tp
composer require topthink/think-swoole
~~~
## 啟動`Swoole HTTP`服務(wù)
第一個場景(也是該擴展最重要的一個場景),畢竟大部分使用`think-swoole`擴展的用戶都是在使用ThinkPHP開發(fā)網(wǎng)站或者項目,使用`think-swoole`擴展可以讓你的產(chǎn)品直接部署到`Swoole`上,并且享受下面的優(yōu)勢:
* 無需對代碼進(jìn)行改造就能帶來性能的數(shù)倍提升;
* 可以在`Apache`/`Nginx`等傳統(tǒng)WEB服務(wù)器和`Swoole`之間切換部署;
> 簡單點說,就是你可以在傳統(tǒng)模式下開發(fā)你的應(yīng)用,然后直接部署到`Swoole`上運行,但無需針對`Swoole`寫任何的處理代碼。
安裝完擴展后,你什么都不需要做,最簡單的就是直接在命令行(應(yīng)用根目錄下面)下執(zhí)行:
~~~cmd
php think swoole
~~~
啟動成功后會顯示
~~~
Starting swoole http server...
Swoole http server started: <http://0.0.0.0:9501>
You can exit with `CTRL-C`
~~~
可以看到已經(jīng)在`0.0.0.0:9501`啟動一個HTTP Server服務(wù)端,下面我們可以直接訪問當(dāng)前的應(yīng)用。
~~~
http://localhost:9501
~~~
如果你之前已經(jīng)有運行一個80端口的WEB服務(wù),那么可以比較下兩個頁面的區(qū)別。
如果你是剛創(chuàng)建的項目,那么可以直接看到ThinkPHP`5.1`的歡迎頁面。

否則你會看到你的項目首頁。
### 配置文件
`HTTPServer`的參數(shù)可以在應(yīng)用配置目錄下的`swoole.php`里面配置,該文件會在擴展安裝的時候自動生成(如果沒有則可以自己創(chuàng)建)。
擴展自帶的配置參數(shù)主要包括:
配置參數(shù) | 描述|默認(rèn)值
--- | --- | ---
host | 監(jiān)聽地址|0.0.0.0
port | 監(jiān)聽端口|9501
mode | 運行模式|SWOOLE_PROCESS
sock_type | Socket type|SWOOLE_SOCK_TCP
app_path | 應(yīng)用目錄(守護(hù)進(jìn)程模式必須設(shè)置)|自動識別
ssl | 是否啟用https|false
file_monitor | 是否監(jiān)控文件更改(V2.0.9+)| false
file_monitor_interval| 監(jiān)控文件間隔(秒)(V2.0.9+)| 2
file_monitor_path | 監(jiān)控目錄 (V2.0.9+)| 默認(rèn)監(jiān)控application和config目錄
> 其它的`swoole`參數(shù)可以參考官方文檔的[配置參數(shù)](https://wiki.swoole.com/wiki/page/274.html),所有`swoole`本身支持的配置參數(shù)都可以直接在`swoole.php`中使用。
### 守護(hù)進(jìn)程模式
如果需要使用守護(hù)進(jìn)程模式運行,可以使用
~~~cmd
php think swoole -d
~~~
或者在`swoole.php`文件中設(shè)置
~~~
'daemonize' => true
~~~
不過一定要記得,如果啟用守護(hù)進(jìn)程模式,必須設(shè)置應(yīng)用目錄`app_path`(使用絕對路徑),否則會出錯。
~~~
'host' => '0.0.0.0', // 監(jiān)聽地址
'port' => 9501, // 監(jiān)聽端口
'daemonize' => true,
'app_path' => '/home/www/tp/application/',
~~~
### 基本操作
如果要停止服務(wù),可以使用
~~~cmd
php think swoole stop
~~~
`reload`服務(wù)
~~~cmd
php think swoole reload
~~~
`stop`服務(wù)
~~~cmd
php think swoole stop
~~~
`restart`服務(wù)
~~~cmd
php think swoole restart
~~~
> `restart`和`reload`的區(qū)別是,`restart`會先`stop`然后`start`,而`reload`則是平滑重啟服務(wù),不會中斷服務(wù)。
如果你需要修改地址和端口,可以修改`swoole.php`配置文件
~~~
'host' => 'tp5.com', // 監(jiān)聽地址
'port' => 8080, // 監(jiān)聽端口
~~~
改完后,需要重啟服務(wù)才能生效
~~~cmd
php think swoole restart
~~~
現(xiàn)在可以直接訪問
~~~
http://tp5.com:8080
~~~
>[danger] 如果你需要設(shè)置`80`端口,需要`root`權(quán)限才可以。
> 如果你安裝的是`2.0.12+`版本的擴展,還可以支持在命令行指定地址和端口,例如:
~~~cmd
php think swoole -H tp.com -p 9508
~~~
如果啟動了多個不同端口的服務(wù),`reload`、`restart`和`stop`操作必須也是針對某個端口的才能正確操作,我們以`reload`操作為例進(jìn)行說明。
如果我們需要`reload`前面啟動的`tp.com:9508`服務(wù),下面的指令是錯誤的
~~~cmd
php think swoole reload
~~~
可能會出現(xiàn)錯誤提示:
~~~
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`類的處理,因此不要調(diào)用(包括依賴注入)`think\Cookie`和`think\Session`類,而應(yīng)該改為`think\swoole\Cookie`類和`think\swoole\Session`類。但`think\facade\Cookie`和`think\facade\Session`類的用法是正常的,因此原來的靜態(tài)方法調(diào)用依然可以正常使用。同時,`Request`對象的`cookie`方法和`session`方法也可以正常使用。
關(guān)于`Swoole`的`Session`的用法,這里要特別強調(diào)下。
>[danger] `Swoole`是沒有`Session`的概念,因此所有PHP內(nèi)置的`session`函數(shù)都是無效的,`think-swoole`擴展單獨封裝了一個`Session`類,和系統(tǒng)的`Session`機制無關(guān)。
該擴展提供的`think\swoole\Session`類是基于`swoole_table`和`ThinkPHP`緩存的混合解決方案。
每次`Session::start()`的時候系統(tǒng)會從`swoole_table`或者定義的緩存類型中獲取當(dāng)前用戶的`Session`數(shù)據(jù),而`session_id`數(shù)據(jù)則通過`Cookie`獲取。并且在當(dāng)前`worker`進(jìn)程中不會過期,但每次從`swoole_table`或者緩存中獲取`session`的時候則會判斷是否過期,`session`的有效期還是通過`session.php`配置文件的`expire`配置參數(shù)進(jìn)行設(shè)置。
> `swoole_table`一個基于共享內(nèi)存和鎖實現(xiàn)的超高性能,并發(fā)數(shù)據(jù)結(jié)構(gòu)。用于解決多進(jìn)程/多線程數(shù)據(jù)共享和同步加鎖問題。
由于`swoole_table`需要事先分配內(nèi)存大小和字段定義,在`swoole.php`配置文件中需要添加定義:
~~~
'table' => [
// 定義最大記錄數(shù)
'size' => 1024,
// 字段類型定義(目前僅支持 string int 和 float類型)
'column' =>[
'data' => ['string',255], // 字符串類型 長度為255個字節(jié)
'expire'=> ['int',8], // 整型 長度為8
],
],
~~~
> `swoole_table`分配的內(nèi)存無法動態(tài)擴容和調(diào)整字段類型,如果修改則需要重啟才能生效。
然后在`session.php`配置文件中,添加
~~~
'use_swoole_table' => true,
~~~
`swoole_table`是一個可選方案。我們更建議使用緩存機制來處理`Session`,如果你的應(yīng)用比較大,則應(yīng)該配置使用`redis`之類的緩存機制更加適合,直接在你的`cache.php`中定義相關(guān)緩存配置即可。
>[danger] 為了避免復(fù)雜,swoole的`Session`類不再支持`prefix`參數(shù),如果需要區(qū)分比如前后臺不同`session`的需求,可以使用`name`參數(shù)進(jìn)行區(qū)分。
### 文件監(jiān)控
由于`Swoole`服務(wù)運行過程中PHP文件是常駐內(nèi)存運行的,這樣可以避免重復(fù)讀取磁盤、重復(fù)解釋編譯PHP,以便達(dá)到最高性能。所以更改業(yè)務(wù)代碼后必須手動`reload`或者`restart`才能生效。
`think-swoole`擴展提供了監(jiān)控文件更新的功能,在檢測到相關(guān)目錄的文件有更新后會自動`reload`,從而不需要手動進(jìn)行`reload`操作,方便開發(fā)調(diào)試。
如果你的應(yīng)用開啟了調(diào)試模式,文件監(jiān)控功能是自動開啟的。原則上,在部署模式下不建議開啟文件監(jiān)控,一方面有性能損耗,另外一方面對文件所做的任何修改都需要確認(rèn)無誤才能進(jìn)行更新部署。如果你確實需要在部署模式下開啟文件監(jiān)控,可以設(shè)置如下:
~~~
'file_monitor' => true, // 開啟文件監(jiān)控
'file_monitor_interval' => 1, // 文件監(jiān)控檢測的時間間隔
'file_monitor_path' => '', // 文件監(jiān)控目錄 一般不需要設(shè)置 默認(rèn)會監(jiān)控應(yīng)用目錄和配置目錄
~~~
### 事件回調(diào)
擴展自帶的`HTTPServer`包含了`onWorkerStart`和`onRequest`兩個事件回調(diào),你如果需要增加其它的回調(diào)事件處理,可以在配置文件中直接添加:
~~~
'WorkerStop' => function($server, $worker_id) {
},
'WorkerError' => function($serv, $worker_id, $worker_pid, $exit_code, $signal) {
},
~~~
關(guān)于事件回調(diào)的具體用法,可以參考`swoole`官方文檔。
>[danger] 如果不熟悉內(nèi)部機制,請勿隨意替換和更改`onWorkerStart`和`onRequest`事件回調(diào),會導(dǎo)致不可預(yù)期的結(jié)果。
### `Nginx+Swoole`部署
可以使用`Nginx`請求轉(zhuǎn)發(fā)到`Swoole`的方式部署,充分發(fā)揮`Nginx`的配置優(yōu)勢和強大功能。
~~~
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;
}
}
~~~
### 靜態(tài)資源訪問
如果你沒有使用`Nginx`代理的話,為了確保靜態(tài)資源的正常訪問,請確認(rèn)下面的參數(shù)配置正確:
~~~
// 網(wǎng)站根目錄位置
'document_root' => Env::get('root_path') . 'public',
// 開啟靜態(tài)資源處理
'enable_static_handler' => true,
~~~
> 使用Chrome瀏覽器會自動請求一次`favicon.ico`,所以確保你的網(wǎng)站根目錄下面有存在`favicon.ico`文件,否則會產(chǎn)生一次`404`請求的錯誤日志。
### `HTTPS`和`HTTP2`支持
如果需要配置你的`HTTP`服務(wù)支持`HTTPS`,需要增加配置如下:
~~~
'ssl' => true,
'ssl_cert_file' => __DIR__.'/ssl.crt',
'ssl_key_file' => __DIR__.'/ssl.key',
~~~
記得準(zhǔn)確指定你的`cert`證書和`key`私鑰的路徑。
>[danger] 使用`SSL`必須在編譯`swoole`時加入`--enable-openssl`選項
>
如果需要支持`HTTP2`協(xié)議,則在SSL支持的基礎(chǔ)上還需要在編譯的時候加入`--enable-http2`選項
~~~
./configure --enable-openssl --enable-http2
~~~
然后在`swoole.php`中增加配置
~~~
'open_http2_protocol' => true
~~~
### 其它注意事項
> 為了讓你的應(yīng)用能夠順利的運行在`Swoole`上面,擴展做了大量的底層處理工作,包括讓你的請求數(shù)據(jù)、`Cookie`和`Session`正常運作。
>[danger] 在`Swoole`下面,不能使用`$_GET`、`$_POST`、`$_REQUEST`、`$_SERVER`、`$_COOKIE`以及`$_SESSION`等原生的PHP用法,只能使用框架提供的類和方法進(jìn)行獲取。
錯誤的用法:
~~~
$name = $_GET['name'];
$name = $_POST['name'];
$name = $_COOKIE['name'];
$name = $_SESSION['name'];
$host = $_SERVER['HTTP_HOST'];
~~~
>[info] `V2.0.11+`版本開始,系統(tǒng)可以支持原生全局變量的獲取,但仍然不建議使用。
>
正確的用法(以下用法都采用了`Facade`靜態(tài)代理類):
~~~
$name = Request::get('name');
$name = Request::param('name');
$name = Cookie::get('name');
$name = Session::get('name');
$host = Request::server('http_host');
~~~
如果你要獲取`php://input`內(nèi)容,必須把原來的代碼
~~~
file_get_contents('php://input');
~~~
改成
~~~
Request::getInput();
~~~
不過更建議使用
~~~
Request::put();
~~~
因為可以支持`json`數(shù)據(jù)的自動解析而不需要手動進(jìn)行`json_decode`。
由于`onWorkerStart`運行的時候還沒有`HTTP_HOST`,因此最好在應(yīng)用配置文件`config/app.php`中設(shè)置`app_host`。
請不要調(diào)用PHP原生的`header`方法,使用`Response`對象的`header`方法替代。
不要使用PHP原生的`session`相關(guān)函數(shù),使用`Session`類的相關(guān)方法。
> 目前為止,尚有一些功能不夠完善(例如文件上傳之類),請期待后續(xù)版本更新。
## 快速啟動`Swoole Server`
現(xiàn)在來看第二個場景,通過簡單的配置快速啟動一個`swoole`服務(wù),包括`WebSocket`/`Http`/`Socket`服務(wù)。
可以支持直接啟動一個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`
~~~
這個時候已經(jīng)在`0.0.0.0:9508`啟動一個`Websocket`服務(wù)。
你可以在瀏覽器中訪問
~~~
http://127.0.0.1:9508
~~~
會看到類似下面的頁面(后面是一串隨機數(shù))。

### 守護(hù)進(jìn)程
如果需要使用守護(hù)進(jìn)程方式運行,可以使用
~~~cmd
php think swoole:server -d
~~~
或者在`swoole.php`文件中設(shè)置
~~~
'daemonize' => true
~~~
### 配置文件
如果需要自定義參數(shù),可以在`config/swoole_server.php`中進(jìn)行配置,包括:
配置參數(shù) | 描述|默認(rèn)值
--- | --- | ---
type| 服務(wù)類型(支持socket、http或者留空)| socket
host | 監(jiān)聽地址|0.0.0.0
port | 監(jiān)聽端口|9508
mode | 運行模式|SWOOLE_PROCESS
sock_type | Socket type|SWOOLE_SOCK_TCP
daemonize|守護(hù)進(jìn)程|false
>[danger] 注意不要和`swoole.php`文件文件混淆,兩者的作用完全不同。
并且支持`swoole`所有的參數(shù),以及支持使用閉包方式定義相關(guān)事件回調(diào)。
~~~
return [
// 擴展自身配置
'host' => '0.0.0.0', // 監(jiān)聽地址
'port' => 9501, // 監(jiān)聽端口
'type' => 'socket', // 服務(wù)類型 支持 socket http或者留空
'mode' => SWOOLE_PROCESS,
'sock_type' => SWOOLE_SOCK_TCP,
// 可以支持swoole的所有配置參數(shù)
'daemonize' => false,
'worker_num' => 4,
// 事件回調(diào)定義
'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";
},
];
~~~
### 自定義服務(wù)類
如果你需要更高級的自定義事件回調(diào),也可以使用自定義的`Swoole`服務(wù)類。
~~~
<?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] 自定義服務(wù)類必須繼承`think\swoole\Server`類,支持`swoole`所有的回調(diào)方法定義(回調(diào)方法必須是`public`類型)。
`serverType` 屬性定義為 `socket`或者`http` 則支持swoole的`swoole_websocket_server`和`swoole_http_server`
然后在`swoole_server.php`中增加配置參數(shù):
~~~
return [
'swoole_class' => 'app\http\Swoole',
];
~~~
> 定義該參數(shù)后,其它配置參數(shù)均不再有效。
然后就可以在命令行啟動服務(wù)端
~~~cmd
php think swoole:server
~~~
一樣可以支持使用守護(hù)進(jìn)程模式運行,
~~~cmd
php think swoole:server -d
~~~
同樣也支持`reload`、`restart`和`stop` 操作。
~~~cmd
php think swoole:server reload
~~~
客戶端代碼的實現(xiàn)有很多,如果你是使用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`服務(wù)
你可以通過命令行的指令啟動多個不同端口的`swoole`服務(wù),例如:
~~~
php think swoole:server -p 9800
php think swoole:server -p 9700
~~~
如果要分別對不同端口的服務(wù)進(jìn)行`stop`操作,務(wù)必使用
~~~
php think swoole:server stop -p 9800
php think swoole:server stop -p 9700
~~~
