# 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`的歡迎頁面。

否則你會看到你的項目首頁。
### 配置文件
`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
~~~
會看到類似下面的頁面(后面是一串隨機數)。

### 守護進程
如果需要使用守護進程方式運行,可以使用
~~~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
~~~