# swoole_server高級特性
---
[TOC=2,3]
---
## **1.改變Worker進程的用戶/組**
在某些情況下,主進程需要使用Root來啟動,比如需要監聽80端口。這時Worker進程的業務代碼也會運行在root用戶下,這是非常不安全的。 業務代碼的漏洞可能會導致整個服務器被攻破,所以需要將Worker進程所屬用戶和組改為其他用戶。 在PHP中使用posix系列函數即可完成此操作。可在swoole的[onWorkerStart](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/02.swoole_server%E4%BA%8B%E4%BB%B6%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0.md#3onworkerstart)回調中加入以下代碼:
```php
$user = posix_getpwnam('www-data');
posix_setuid($user['uid']);
posix_setgid($user['gid']);
```
另外,在PHP代碼中訪問/etc/目錄,就是指文件系統的/etc/,這樣是不安全的。比如PHP代碼中誤操作執行**rm -rf /**。會帶來嚴重的后果。 可以使用chroot函數,將根目錄重定向到另外一個安全的目錄。
```php
chroot('/tmp/root/');
```
## **2.回調函數中的from_id和fd**
- from_id是來自于哪個reactor線程
- fd是tcp連接的文件描述符
調用swoole_server_send/swoole_server_close函數需要傳入這兩個參數才能被正確的處理。如果業務中需要發送廣播,可以先通過[connection_list](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/03.swoole_server%E5%87%BD%E6%95%B0%E5%88%97%E8%A1%A8.md#swoole_serverconnection_list)獲取全部連接的fd,然后通過[connection_info](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/03.swoole_server%E5%87%BD%E6%95%B0%E5%88%97%E8%A1%A8.md#swoole_serverconnection_info)查詢到fd對應的from_id。
## **3.Buffer和EOF_Check的使用**
在外網通信時,有些客戶端發送數據的速度較慢,每次只能發送一小段數據。這樣[onReceive](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/02.swoole_server%E4%BA%8B%E4%BB%B6%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0.md#1onreceive)到的數據就不是一個完整的包。 還有些客戶端是逐字節發送數據的,如果每次回調[onReceive](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/02.swoole_server%E4%BA%8B%E4%BB%B6%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0.md#1onreceive)會拖慢整個系統。<br>
Swoole提供了buffer和eof_check的功能,在C擴展底層檢測到如果不是完整的請求,會等待新的數據到達,組成完成的請求后再回調[onReceive](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/02.swoole_server%E4%BA%8B%E4%BB%B6%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0.md#1onreceive)。<br>
在[swoole_server_set](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/03.swoole_server%E5%87%BD%E6%95%B0%E5%88%97%E8%A1%A8.md#swoole_serverset)中增加,[open_eof_check](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#13open_eof_check)和[package_eof](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#14package_eof-)來開啟此功能。[open_eof_check](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#13open_eof_check)=1表示啟用buffer檢查,[package_eof](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#14package_eof-)設置數據包結束符<br>
> buffer功能會將所有收到的數據放到內存中,會占用較多內存<br>
通過設置[package_max_length](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#19package_max_length)<br> 來設定每個連接最大緩存多少數據,超過此大小的連接將會被關閉<br>
```php
swoole_server_set($serv, array(
'package_eof' => "\r\n\r\n", //http協議就是以\r\n\r\n作為結束符的,這里也可以使用二進制內容
'open_eof_check' => 1,
));
```
## **4.固定包頭+包體協議自動分包**
swoole-1.7.3版本重構了length_check特性的代碼,對于固定包頭+包體格式的協議可以直接在master進程中進行分包和組包,worker進程中可以一次性收到一個完整的包。帶來的好處是:<br>
- C擴展層進行協議的處理,性能最佳,原PHP代碼雖然也可以實現協議處理,但需要耗費較多CPU
- TCP連接與業務邏輯分離,有效利用所有Worker進程,即使只有1個TCP連接,也可以利用所有Worker
使用時,僅需打開[open_length_check](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#15open_length_check)配置項,并設置相應的配置選項即可。<br>
相關配置項查看[swoole_server配置選項](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server配置選項.md)第15項到第19項
## **5.Worker與Reactor通信模式**
Worker進程如何與Reactor進程通信,Swoole提供了3種方式。通過[swoole_server_set](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/03.swoole_server%E5%87%BD%E6%95%B0%E5%88%97%E8%A1%A8.md#swoole_serverset)參數中修改[dispatch_mode](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#5dispatch_mode)的值來配置。<br>
| dispatch_mode | 名稱 | 介紹 |
| ---- | ----- | ---- |
| 1 | 輪詢模式 | 收到的請求數據包會輪詢發到每個Worker進程。 |
| 2 | FD取模 | 數據包根據fd的值%worker_num來分配,這個模式可以保證一個TCP客戶端連接發送的數據總是會被分配給同一個worker進程。 |
| 3 | Queue模式 | 此模式下,網絡請求的處理是搶占式的,這可以保證總是最空閑的worker進程才會拿到請求去處理,缺點是客戶端連接對應的worker是隨機的。不確定哪個worker會處理請求。無法保存連接狀態(可借助第三方庫或者[swoole_table](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/13.swoole_table.md)實現)|
## **6.TCP-Keepalive死連接檢測**
在TCP中有一個Keep-Alive的機制可以檢測死連接,應用層如果對于死鏈接周期不敏感或者沒有實現心跳機制,可以使用操作系統提供的keepalive機制來踢掉死鏈接。 在[swoole_server_set](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/03.swoole_server%E5%87%BD%E6%95%B0%E5%88%97%E8%A1%A8.md#swoole_serverset)中增加[open_tcp_keepalive](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#24open_tcp_keepalive)=>1表示啟用tcp keepalive。 另外,有3個選項可以對keepalive的細節進行調整。<br>
| 名稱 | 介紹 |
| ---- | ----- |
| tcp_keepidle | 單位秒,連接在n秒內沒有數據請求,將開始對此連接進行探測 |
| tcp_keepcount | 探測的次數,超過次數后將close此連接 |
| tcp_keepinterval | 探測的間隔時間,單位秒 |
## **7.TCP服務器心跳維持方案**
正常情況下客戶端中斷TCP連接時,會發送一個FIN包,進行4次斷開握手來通知服務器。但一些異常情況下,如客戶端突然斷電斷網或者網絡異常,服務器可能無法得知客戶端已斷開連接。<br>
尤其是移動網絡,TCP連接非常不穩定,所以需要一套機制來保證服務器和客戶端之間連接的有效性。<br>
Swoole擴展本身內置了這種機制,開發者只需要配置兩個個參數即可啟用。Swoole在每次收到客戶端數據會記錄一個時間戳,當客戶端在一定時間內未向服務器端發送數據,那服務器會自動切斷連接。<br>
```php
$serv->set(array(
'heartbeat_check_interval' => 5,
'heartbeat_idle_time' => 10
));
```
上面的設置就是每5秒偵測一次心跳,一個TCP連接如果在10秒內未向服務器端發送數據,將會被切斷。[點此查看參數說明](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#11heartbeat_check_interval)<br>
使用[swoole_server::heartbeat()](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/03.swoole_server%E5%87%BD%E6%95%B0%E5%88%97%E8%A1%A8.md#swoole_serverheartbeat)函數手工檢測心跳是否到期。此函數會返回閑置時間超過[heartbeat_idle_time](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#12heartbeat_idle_time)的所有TCP連接。程序中可以將這些連接做一些操作,如發送數據或關閉連接。<br>
## **8.多端口監聽的使用**
Swoole提供了多端口監聽的機制,這樣可以同時監聽UDP和TCP,同時監聽內網地址和外網地址。內網地址和端口用于管理,外網地址用于對外服務。<br>
[點此查看教程](https://github.com/LinkedDestiny/swoole-doc/blob/master/04.Swoole%E5%A4%9A%E7%AB%AF%E5%8F%A3%E7%9B%91%E5%90%AC%E3%80%81%E7%83%AD%E9%87%8D%E5%90%AF%E4%BB%A5%E5%8F%8ATimer%E8%BF%9B%E9%98%B6%EF%BC%9A%E7%AE%80%E5%8D%95crontab.md#1%E5%A4%9A%E7%AB%AF%E5%8F%A3%E7%9B%91%E5%90%AC)<br>
## **9.捕獲Server運行期致命錯誤**
Server運行期一旦發生致命錯誤,那客戶端連接將無法得到回應。如Web服務器,如果有致命錯誤應當向客戶端發送Http 500 錯誤信息。<br>
在PHP中可以通過register_shutdown_function + error_get_last 2個函數來捕獲致命錯誤,并將錯誤信息發送給客戶端連接。具體代碼示例如下:<br>
```php
register_shutdown_function('handleFatal');
function handleFatal()
{
$error = error_get_last();
if (isset($error['type']))
{
switch ($error['type'])
{
case E_ERROR :
case E_PARSE :
case E_DEPRECATED:
case E_CORE_ERROR :
case E_COMPILE_ERROR :
$message = $error['message'];
$file = $error['file'];
$line = $error['line'];
$log = "$message ($file:$line)\nStack trace:\n";
$trace = debug_backtrace();
foreach ($trace as $i => $t)
{
if (!isset($t['file']))
{
$t['file'] = 'unknown';
}
if (!isset($t['line']))
{
$t['line'] = 0;
}
if (!isset($t['function']))
{
$t['function'] = 'unknown';
}
$log .= "#$i {$t['file']}({$t['line']}): ";
if (isset($t['object']) && is_object($t['object']))
{
$log .= get_class($t['object']) . '->';
}
$log .= "{$t['function']}()\n";
}
if (isset($_SERVER['REQUEST_URI']))
{
$log .= '[QUERY] ' . $_SERVER['REQUEST_URI'];
}
error_log($log);
$serv->send($this->currentFd, $log);
}
}
}
```
## **10.SSL隧道加密TCP-Server**
1.7.4后swoole增加了對SSL隧道加密的支持,在swoole_server中可以啟用SSL證書加密。使用僅需增加[swoole_server_set](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/03.swoole_server%E5%87%BD%E6%95%B0%E5%88%97%E8%A1%A8.md#swoole_serverset)的配置即可,并將listener端口的類型,增加[SWOOLE_SSL](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/03.swoole_server%E5%87%BD%E6%95%B0%E5%88%97%E8%A1%A8.md#swoole_serveraddlistener)標志。<br>
```php
$serv = new swoole_server("0.0.0.0", 443, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL); // 加密端口
$key_dir = dirname(dirname(__DIR__)).'/tests/ssl';
$serv->addlistener('0.0.0.0', 80, SWOOLE_SOCK_TCP); // 非加密端口
$serv->set(array(
'worker_num' => 4,
'ssl_cert_file' => $key_dir.'/ssl.crt',
'ssl_key_file' => $key_dir.'/ssl.key',
));
```
配置項說明[點此查看](https://github.com/LinkedDestiny/swoole-doc/blob/master/doc/01.swoole_server%E9%85%8D%E7%BD%AE%E9%80%89%E9%A1%B9.md#23ssl_cert_file%E5%92%8Cssl_key_file).<br>
- swoole簡介
- swoole功能概述
- 序章
- 開發必讀
- 1 環境搭建
- 1.1 環境搭建
- 1.2 搭建Echo服務器
- 2 初識Swoole
- 2.1 Worker進程
- 2.2 TaskWorker進程
- 2.3 Timer定時器
- 2.4 Process進程
- 2.5 Table內存表
- 2.6 多端口監聽
- 2.7 sendfile文件支持
- 2.8 SSL支持
- 2.9 熱重啟
- 2.10 http_server
- 附錄*server配置
- 附錄*server函數
- 附錄*server屬性
- 附錄*server回調函數
- 附錄*server高級特性
- 心跳檢測
- 3 Swoole協議
- 3.1 EOF協議
- 3.2 固定包頭協議
- 3.3 Http協議
- 3.4 WebSocket協議
- 3.5 MTQQ協議
- 內置http_server
- 內置websocket_server
- Swoole\Redis\Server
- 4 Swoole異步IO
- 4.1 AsyncIO
- 異步文件系統IO
- swoole_async_readfile
- swoole_async_writefile
- swoole_async_read
- swoole_async_write
- 5 swoole異步客戶端
- ws_client
- http_client
- mysql_client
- redis_client
- tcp_client
- http2_client
- 6 swoole協程
- Swoole\Coroutine\Http\Client
- Swoole\Coroutine\MySQL
- Swoole\Coroutine\Redis
- Coroutine\PostgreSQL
- Swoole\Coroutine\Client
- Swoole\Coroutine\Socket
- Swoole\Coroutine\Channel
- Coroutine
- Swoole\Coroutine::create
- Swoole\Coroutine::resume
- Swoole\Coroutine::suspend
- Swoole\Coroutine::sleep
- Coroutine::getaddrinfo
- Coroutine::gethostbyname
- swoole_async_dns_lookup_coro
- Swoole\Coroutine::getuid
- getDefer
- setDefer
- recv
- Coroutine::stats
- Coroutine::fread
- Coroutine::fget
- Coroutine::fwrite
- Coroutine::readFIle
- Coroutine::writeFIle
- Coroutine::exec
- 7 swoole_process
- process::construct
- process::start
- process::name
- process::signal
- process::setaffinity
- process::exit
- process::kill
- process::daemon
- process->exec
- process::wait
- process::alarm
- 8 swoole定時器
- swoole_timer_tick
- swoole_timer_after
- swoole_timer_clear
- 9 swoole_event
- swoole_event_add
- swoole_event_set
- swoole_event_del
- swoole_event_wait
- swoole_event_defer
- swoole_event_write
- swoole_event_exit
- swoole提供的function
- 常見問題
- 客戶端鏈接失敗原因
- 如何設置進程數
- 如何實現異步任務
- 如何選擇swoole三種模式
- php中哪些函數是阻塞的
- 是否可以共用1個redis或mysql連接
- 如何在回調函數中訪問外部的變量
- 為什么不要send完后立即close
- 不同的Server程序實例間如何通信
- MySQL的連接池、異步、斷線重連
- 在php-fpm或apache中使用swoole
- 學習Swoole需要掌握哪些基礎知識
- 在phpinfo中有在php-m中沒有
- 同步阻塞與異步非阻塞選擇
- CURL發送POST請求服務器端超時
- 附錄
- 預定義常量
- 內核參數調優
- php四種回調寫法
- 守護進程程序常用數據結構
- swoole生命周期
- swoole_server中內存管理機制
- 使用jemalloc優化swoole內存分配性能
- Reactor、Worker、Task的關系
- Manager進程
- Swoole的實現
- Reactor線程
- 安裝擴展
- swoole-worker手冊
- swoole相關開源項目
- 寫在后面的話
- 版本更新記錄
- 4.0