本文示例代碼詳見:https://github.com/52fhy/swoole_demo。php
Swoole是一個PHP擴展,提供了PHP語言的異步多線程服務器,異步TCP/UDP網絡客戶端,異步MySQL,異步Redis,數據庫鏈接池,AsyncTask,消息隊列,毫秒定時器,異步文件讀寫,異步DNS查詢。 Swoole內置了Http/WebSocket服務器端/客戶端、Http2.0服務器端。html
Swoole: PHP的異步、並行、高性能網絡通訊引擎
http://www.swoole.com/react
Github:
https://github.com/swoole
https://github.com/matyhtfc++
Swoole須要使用源碼安裝。暫無Windows版擴展。git
wget -O swoole.zip https://github.com/swoole/swoole-src/archive/v1.9.11.zip unzip swoole.zip cd swoole phpize ./configure make && make install
因爲pecl是須要編譯的,因此須要先安裝編譯器(已安裝編譯器能夠忽略):github
yum install -y gcc gcc-c++ make cmake bison autoconf
而後:redis
pecl install swoole pecl install redis
pecl安裝擴展完成後會提示添加so文件到php.ini。示例:shell
Build process completed successfully Installing '/usr/lib64/php/modules/swoole.so' install ok: channel://pecl.php.net/swoole-1.9.11 configuration option "php_ini" is not set to php.ini location You should add "extension=swoole.so" to php.ini
添加示例:數據庫
[swoole] extension = /usr/lib64/php/modules/swoole.so
建議使用的版本(截止時間2017-6-3)編程
穩定版:v1.9.9 預覽版:v2.0.7
1.9.x
分支已進入特性鎖按期,再也不開發新功能,僅修復BUG。
最低版本:
建議1.8.6+
。PHP7建議使用1.9.2+
。
建議使用的PHP版本
PHP5.5或更高版本 PHP7.0.13或更高版本
使用
TP3.1+
框架的朋友升級到PHP7.1.0
可能會出現rewrite失效問題。建議PHP7.0.x
系列。
快速查看當前swoole的版本:
php --ri swoole
1.8.6~1.8.13
都是小範圍BUG修復及功能新增。其中 1.8.11
增長SIGRTMIN信號處理函數,用於從新打開日誌文件。
1.8.6
版本是一個重要的BUG修復版本,主要修復了PHP7環境下HttpServer、TCPClient、HttpClient、Redis等客戶端存在的內存泄漏、崩潰問題。
1.9.0
版本增長了多項新特性,修復了多個已知問題。1.9版本是100%向下兼容1.8的,用戶可無縫升級。
1.9.1
修復PHP7下啓用opcache致使崩潰的問題;重構reopen log file
特性,收到SIGRTMIN
信號後從新打開日誌文件並重定向標準輸出 等。
1.9.2
修復PHP7下發生zend_mm_heap corrupted
的問題 等。
1.9.4
修復WebSocket服務器默認onRequest方法內存泄漏問題 等。
1.9.5
增長pid_file選項,在Server啓動時將主進程ID寫入指定的文件 等。
1.9.6
修復添加超過1萬個以上定時器時發生崩潰的問題;增長swoole_serialize模塊,PHP7下高性能序列化庫;修復監聽UDP端口設置onPacket無效的問題 等。
1.9.9
修復Http2客戶端POST數據時協議錯誤問題 等。
1.9.11
修復WebSocket服務器onOpen回調函數存在內存泄漏的問題;修復Http服務器文件上傳在5.6版本發生崩潰的問題;優化添加Task和Timer的定時器性能,提高分支預測成功率 等。
Swoole目前總共有三種運行模式,默認爲多進程模式(SWOOLE_PROCESS
)。
# Base模式(SWOOLE_BASE) 傳統的異步非阻塞Server,reactor和worker是同一個角色。TCP鏈接是在worker進程中維持的。 若是客戶端鏈接之間不須要交互,可使用BASE模式。如Memcache、Http服務器等。 # 線程模式 多線程Worker模式,Reactor線程來處理網絡事件輪詢,讀取數據。獲得的請求交給Worker線程去處理。 缺點:一個線程發生內存錯誤,整個進程會所有結束。 因爲PHP的ZendVM在多線程模式存在內存錯誤,多線程模式在v1.6.0版本後已關閉。 # 進程模式 與多線程Worker模式不一樣的是,線程換成了進程。Reactor線程來處理網絡事件輪詢,讀取數據。獲得的請求交給Worker進程去處理。適合業務邏輯很是複雜的場景。如WebSocket服務器等。
$serv = new swoole_server(string $host, int $port, int $mode = SWOOLE_PROCESS, int $sock_type = SWOOLE_SOCK_TCP);
咱們來使用實例進行分析:
<?php $server = new \swoole_server("127.0.0.1",8088);//默認是多進程模式、TCP類型 $server->on('connect', function ($serv, $fd){ }); $server->on('receive', function ($serv, $fd, $from_id, $data){ }); $server->on('close', function ($serv, $fd){ }); $server -> start();
繼續在Shell中輸入如下命令:
php swoole_tcp_server.php pstree -ap|grep swoole_tcp_server | | `-php,2454 swoole_tcp_server.php | | |-php,2456 swoole_tcp_server.php | | | `-php,2458 swoole_tcp_server.php
從系統的輸出中,咱們能夠很容看出server其實有3個進程,進程的pid分別是245四、245六、2458,其中2454是2456的父進程,而2456又是2458的父進程。
因此,其實咱們雖然看起來只是啓動了一個Server,其實最後產生的是三個進程。
這三個進程中,全部進程的根進程(2454),就是所謂的Master
進程;而2456進程,則是Manager
進程;最後的2458進程,是Worker
進程。
基於此,咱們簡單梳理一下,當執行的start方法以後,發生了什麼:
非守護進程模式下,則當前進程直接做爲Master進程工做。
因此,一個最基礎的Swoole Server,至少須要有3個進程,分別是Master進程、Manager進程和Worker進程。
事實上,一個多進程模式下的Swoole Server中,有且只有一個Master進程;有且只有一個Manager進程;卻能夠有n個Worker進程。
Master
進程是一個多線程進程,其中有一組很是重要的線程,叫作Reactor
線程(組),每當一個客戶端鏈接上服務器的時候,都會由Master進程從已有的Reactor線程中,根據必定規則挑選一個,專門負責向這個客戶端提供維持連接、處理網絡IO與收發數據等服務。分包拆包等功能也是在這裏完成。
Manager
進程,某種意義上能夠看作一個代理層,它自己並不直接處理業務,其主要工做是將Master進程中收到的數據轉交給Worker進程,或者將Worker進程中但願發給客戶端的數據轉交給Master進程進行發送。
Manager
進程還負責監控Worker進程,若是Worker進程由於某些意外掛了,Manager進程會從新拉起新的Worker進程,有點像Supervisor的工做。而這個特性,也是最終實現熱重載的核心機制。
Worker
進程其實就是處理各類業務工做的進程,Manager將數據包轉交給Worker進程,而後Worker進程進行具體的處理,並根據實際狀況將結果反饋給客戶端。
咱們能夠總結出來上面簡單的Server,當客戶端鏈接的時候這個過程當中,三種進程之間是怎麼協做的:
Swoole進程/線程結構圖:
如今,咱們基於上面的例子修改代碼,來看看一個簡單的多進程Swoole Server的幾個基本配置:
<?php $server->set(array( 'demonize' => false,//是否後臺運行 'reactor_num' => 2, 'worker_num' => 4 )); $server -> start();
reactor_num
:表示Master進程中,Reactor線程總共開多少個,注意,這個可不是越多越好,由於計算機的CPU是有限的,因此通常設置爲與CPU核心數量相同,或者兩倍便可。
worker_num
:表示啓動多少個Worker進程,一樣,Worker進程數量不是越多越好,仍然設置爲與CPU核心數量相同,或者兩倍便可。
咱們能夠在Shell裏運行,使用pstree查看進程模型結構:
php swoole_tcp_server.php pstree -ap|grep swoole_tcp | | `-php,2505 swoole_tcp_server.php | | |-php,2507 swoole_tcp_server.php | | | |-php,2510 swoole_tcp_server.php | | | |-php,2511 swoole_tcp_server.php | | | |-php,2512 swoole_tcp_server.php | | | `-php,2513 swoole_tcp_server.php
Swoole做爲Server時,回調函數有不少。但能夠簡單分個類:
1) 進程啓動時執行的:onStart、onManagerStart、onWorkerStart;onWorkerStop、onManagerStop、onShutdown;onWorkerError
2) 客戶端交互時觸發的:onReceive/onRequest/onPacket/onMessage、onOpen/onConnect、onClose
3) Task:onTask、onFinish
4) Timer:onTimer
事件執行順序:
$server->start
後發生onShutdown
onStart/onManagerStart/onWorkerStart
會在不一樣的進程內併發執行。onReceive/onConnect/onClose/onTimer
在worker進程(包括task進程)中各自觸發用onWorkerStart/onWorkerStop
onTask
事件僅在task進程中發生onStart/onManagerStart/onWorkerStart
3個事件的執行順序是不肯定的onReceive
事件,沒有onConnect/onClose
事件onPacket
回調函數,收到UDP數據包默認會回調onReceive
函數onOpen
事件回調是可選的:當WebSocket客戶端與服務器創建鏈接並完成握手後會回調此函數實際使用的時候不是全部回調均可以使用的,例如UDP服務器沒有onConnect/onClose
;例如接收數據,在WebSocket裏使用onReceive,在HttpServer使用onRequest,在UDPServer使用onPacket。
示例:
<?php $server = new \swoole_server("127.0.0.1",8088); $server->set(array( 'daemonize' => false, 'reactor_num' => 2, 'worker_num' => 4 )); $server->on('connect', function ($serv, $fd){ echo "client connect. fd is {$fd}\n"; }); $server->on('receive', function ($serv, $fd, $from_id, $data){ echo "client connect. fd is {$fd}\n"; }); $server->on('close', function ($serv, $fd){ echo "client close. fd is {$fd}\n"; }); // 如下回調發生在Master進程 $server->on("start", function (\swoole_server $server){ echo "On master start.\n"; }); $server->on('shutdown', function (\swoole_server $server){ echo "On master shutdown.\n"; }); // 如下回調發生在Manager進程 $server->on('ManagerStart', function (\swoole_server $server){ echo "On manager start.\n"; }); $server->on('ManagerStop', function (\swoole_server $server){ echo "On manager stop.\n"; }); // 如下回調也發生在Worker進程 $server->on('WorkerStart', function (\swoole_server $server, $worker_id){ echo "Worker start\n"; }); $server->on('WorkerStop', function(\swoole_server $server, $worker_id){ echo "Worker stop\n"; }); $server->on('WorkerError', function(\swoole_server $server, $worker_id, $worker_pid, $exit_code){ echo "Worker error\n"; }); $server -> start();
sleep
以及其餘睡眠函數,這樣會致使整個進程阻塞exit/die
是危險的,會致使worker進程退出register_shutdown_function
來捕獲致命錯誤,在進程異常退出時作一些請求工做,具體參看/wiki/page/305.htmltry/catch
捕獲異常,不然會致使工做進程退出set_exception_handler
,必須使用try/catch
方式處理異常Redis
或MySQL
等網絡服務客戶端,Redis/MySQL建立鏈接的相關代碼能夠放到onWorkerStart
回調函數中。緣由是若是共用1個鏈接,那麼返回的結果沒法保證被哪一個進程處理。持有鏈接的進程理論上均可以對這個鏈接進行讀寫,這樣數據就發生錯亂了。具體參考/wiki/page/325.html