本文示例代碼詳見:https://github.com/52fhy/swoole_demo湖蜕。
簡介
Swoole是一個(gè)PHP擴(kuò)展,提供了PHP語言的異步多線程服務(wù)器宋列,異步TCP/UDP網(wǎng)絡(luò)客戶端昭抒,異步MySQL,異步Redis炼杖,數(shù)據(jù)庫連接池灭返,AsyncTask,消息隊(duì)列坤邪,毫秒定時(shí)器熙含,異步文件讀寫,異步DNS查詢艇纺。 Swoole內(nèi)置了Http/WebSocket服務(wù)器端/客戶端怎静、Http2.0服務(wù)器端。
Swoole: PHP的異步黔衡、并行蚓聘、高性能網(wǎng)絡(luò)通信引擎
http://www.swoole.com/
Github:
https://github.com/swoole
https://github.com/matyhtf
安裝
Swoole需要使用源碼安裝。暫無Windows版擴(kuò)展盟劫。
源碼安裝
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安裝
由于pecl是需要編譯的夜牡,所以需要先安裝編譯器(已安裝編譯器可以忽略):
yum install -y gcc gcc-c++ make cmake bison autoconf
然后:
pecl install swoole
pecl install redis
pecl安裝擴(kuò)展完成后會(huì)提示添加so文件到php.ini。示例:
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
Swoole的進(jìn)程模型
運(yùn)行模式
Swoole目前總共有三種運(yùn)行模式侣签,默認(rèn)為多進(jìn)程模式(SWOOLE_PROCESS
)氯材。其中Base模式基本沒有生產(chǎn)應(yīng)用價(jià)值渣锦;協(xié)程模式暫時(shí)還處于預(yù)覽階段;還有一種就是Swoole的多進(jìn)程模式氢哮,也是官方目前最推薦用于生產(chǎn)環(huán)境的模式袋毙。
$serv = new swoole_server(string $host, int $port, int $mode = SWOOLE_PROCESS, int $sock_type = SWOOLE_SOCK_TCP);
實(shí)例分析
我們來使用實(shí)例進(jìn)行分析:
<?php
$server = new \swoole_server("127.0.0.1",8088);//默認(rèn)是多進(jìn)程模式锅知、TCP類型
$server->on('connect', function ($serv, $fd){ });
$server->on('receive', function ($serv, $fd, $from_id, $data){ });
$server->on('close', function ($serv, $fd){ });
$server -> start();
繼續(xù)在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
從系統(tǒng)的輸出中阔拳,我們可以很容看出server其實(shí)有3個(gè)進(jìn)程,進(jìn)程的pid分別是2454幢痘、2456裂七、2458皆看,其中2454是2456的父進(jìn)程,而2456又是2458的父進(jìn)程背零。
所以腰吟,其實(shí)我們雖然看起來只是啟動(dòng)了一個(gè)Server,其實(shí)最后產(chǎn)生的是三個(gè)進(jìn)程徙瓶。
這三個(gè)進(jìn)程中毛雇,所有進(jìn)程的根進(jìn)程(2454),就是所謂的Master
進(jìn)程侦镇;而2456進(jìn)程灵疮,則是Manager
進(jìn)程;最后的2458進(jìn)程壳繁,是Worker
進(jìn)程震捣。
基于此,我們簡單梳理一下闹炉,當(dāng)執(zhí)行的start方法之后蒿赢,發(fā)生了什么:
- 守護(hù)進(jìn)程模式下,當(dāng)前進(jìn)程fork出Master進(jìn)程渣触,然后退出诉植,Master進(jìn)程觸發(fā)OnMasterStart事件。
- Master進(jìn)程啟動(dòng)成功之后昵观,fork出Manager進(jìn)程晾腔,并觸發(fā)OnManagerStart事件。
- Manager進(jìn)程啟動(dòng)成功時(shí)候啊犬,fork出Worker進(jìn)程灼擂,并觸發(fā)OnWorkerStart事件。
非守護(hù)進(jìn)程模式下觉至,則當(dāng)前進(jìn)程直接作為Master進(jìn)程工作剔应。
所以,一個(gè)最基礎(chǔ)的Swoole Server,至少需要有3個(gè)進(jìn)程峻贮,分別是Master進(jìn)程席怪、Manager進(jìn)程和Worker進(jìn)程。
事實(shí)上纤控,一個(gè)多進(jìn)程模式下的Swoole Server中挂捻,有且只有一個(gè)Master進(jìn)程;有且只有一個(gè)Manager進(jìn)程船万;卻可以有n個(gè)Worker進(jìn)程刻撒。
進(jìn)程模型
Master
進(jìn)程是一個(gè)多線程進(jìn)程,其中有一組非常重要的線程耿导,叫做Reactor
線程(組)声怔,每當(dāng)一個(gè)客戶端連接上服務(wù)器的時(shí)候,都會(huì)由Master進(jìn)程從已有的Reactor線程中舱呻,根據(jù)一定規(guī)則挑選一個(gè)醋火,專門負(fù)責(zé)向這個(gè)客戶端提供維持鏈接、處理網(wǎng)絡(luò)IO與收發(fā)數(shù)據(jù)等服務(wù)箱吕。分包拆包等功能也是在這里完成芥驳。
Manager
進(jìn)程,某種意義上可以看做一個(gè)代理層殖氏,它本身并不直接處理業(yè)務(wù)晚树,其主要工作是將Master進(jìn)程中收到的數(shù)據(jù)轉(zhuǎn)交給Worker進(jìn)程姻采,或者將Worker進(jìn)程中希望發(fā)給客戶端的數(shù)據(jù)轉(zhuǎn)交給Master進(jìn)程進(jìn)行發(fā)送雅采。
Manager
進(jìn)程還負(fù)責(zé)監(jiān)控Worker進(jìn)程,如果Worker進(jìn)程因?yàn)槟承┮馔鈷炝丝祝琈anager進(jìn)程會(huì)重新拉起新的Worker進(jìn)程婚瓜,有點(diǎn)像Supervisor的工作。而這個(gè)特性刑棵,也是最終實(shí)現(xiàn)熱重載的核心機(jī)制巴刻。
Worker
進(jìn)程其實(shí)就是處理各種業(yè)務(wù)工作的進(jìn)程,Manager將數(shù)據(jù)包轉(zhuǎn)交給Worker進(jìn)程蛉签,然后Worker進(jìn)程進(jìn)行具體的處理胡陪,并根據(jù)實(shí)際情況將結(jié)果反饋給客戶端。
我們可以總結(jié)出來上面簡單的Server碍舍,當(dāng)客戶端連接的時(shí)候這個(gè)過程中柠座,三種進(jìn)程之間是怎么協(xié)作的:
- Client主動(dòng)Connect的時(shí)候,Client實(shí)際上是與Master進(jìn)程中的某個(gè)Reactor線程發(fā)生了連接片橡。
- 當(dāng)TCP的三次握手成功了以后妈经,由這個(gè)Reactor線程將連接成功的消息告訴Manager進(jìn)程,再由Manager進(jìn)程轉(zhuǎn)交給Worker進(jìn)程。
- 在這個(gè)Worker進(jìn)程中觸發(fā)了OnConnect的方法吹泡。
- 當(dāng)Client向Server發(fā)送了一個(gè)數(shù)據(jù)包的時(shí)候骤星,首先收到數(shù)據(jù)包的是Reactor線程,同時(shí)Reactor線程會(huì)完成組包爆哑,再將組好的包交給Manager進(jìn)程洞难,由Manager進(jìn)程轉(zhuǎn)交給Worker。
- 此時(shí)Worker進(jìn)程觸發(fā)OnReceive事件泪漂。
- 如果在Worker進(jìn)程中做了什么處理廊营,然后再用Send方法將數(shù)據(jù)發(fā)回給客戶端時(shí),數(shù)據(jù)則會(huì)沿著這個(gè)路徑逆流而上萝勤。
Swoole進(jìn)程/線程結(jié)構(gòu)圖:
現(xiàn)在露筒,我們基于上面的例子修改代碼,來看看一個(gè)簡單的多進(jìn)程Swoole Server的幾個(gè)基本配置:
<?php
$server->set(array(
'demonize' => false,//是否后臺(tái)運(yùn)行
'reactor_num' => 2,
'worker_num' => 4
));
$server -> start();
reactor_num
:表示Master進(jìn)程中敌卓,Reactor線程總共開多少個(gè)慎式,注意,這個(gè)可不是越多越好趟径,因?yàn)橛?jì)算機(jī)的CPU是有限的瘪吏,所以一般設(shè)置為與CPU核心數(shù)量相同,或者兩倍即可蜗巧。
worker_num
:表示啟動(dòng)多少個(gè)Worker進(jìn)程掌眠,同樣,Worker進(jìn)程數(shù)量不是越多越好幕屹,仍然設(shè)置為與CPU核心數(shù)量相同蓝丙,或者兩倍即可。
我們可以在Shell里運(yùn)行望拖,使用pstree查看進(jìn)程模型結(jié)構(gòu):
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
回調(diào)函數(shù)
Swoole作為Server時(shí)渺尘,回調(diào)函數(shù)有很多。但可以簡單分個(gè)類:
- 進(jìn)程啟動(dòng)時(shí)執(zhí)行的:onStart说敏、onManagerStart鸥跟、onWorkerStart;onWorkerStop盔沫、onManagerStop医咨、onShutdown;onWorkerError
- 客戶端交互時(shí)觸發(fā)的:onReceive/onRequest/onPacket/onMessage架诞、onOpen/onConnect拟淮、onClose
- Task:onTask、onFinish
- Timer:onTimer
事件執(zhí)行順序:
- 所有事件回調(diào)均在
$server->start
后發(fā)生 - 服務(wù)器關(guān)閉程序終止時(shí)最后一次事件是
onShutdown
- 服務(wù)器啟動(dòng)成功后侈贷,
onStart/onManagerStart/onWorkerStart
會(huì)在不同的進(jìn)程內(nèi)并發(fā)執(zhí)行惩歉。 -
onReceive/onConnect/onClose/onTimer
在worker進(jìn)程(包括task進(jìn)程)中各自觸發(fā) - worker/task進(jìn)程啟動(dòng)/結(jié)束時(shí)會(huì)分別調(diào)
用onWorkerStart/onWorkerStop
-
onTask
事件僅在task進(jìn)程中發(fā)生 - onFinish事件僅在worker進(jìn)程中發(fā)生
-
onStart/onManagerStart/onWorkerStart
3個(gè)事件的執(zhí)行順序是不確定的 - UDP協(xié)議下只有
onReceive
事件等脂,沒有onConnect/onClose
事件 - 如果未設(shè)置
onPacket
回調(diào)函數(shù),收到UDP數(shù)據(jù)包默認(rèn)會(huì)回調(diào)onReceive
函數(shù) -
onOpen
事件回調(diào)是可選的:當(dāng)WebSocket客戶端與服務(wù)器建立連接并完成握手后會(huì)回調(diào)此函數(shù)
實(shí)際使用的時(shí)候不是所有回調(diào)都可以使用的撑蚌,例如UDP服務(wù)器沒有onConnect/onClose
上遥;例如接收數(shù)據(jù),在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";
});
// 以下回調(diào)發(fā)生在Master進(jìn)程
$server->on("start", function (\swoole_server $server){
echo "On master start.\n";
});
$server->on('shutdown', function (\swoole_server $server){
echo "On master shutdown.\n";
});
// 以下回調(diào)發(fā)生在Manager進(jìn)程
$server->on('ManagerStart', function (\swoole_server $server){
echo "On manager start.\n";
});
$server->on('ManagerStop', function (\swoole_server $server){
echo "On manager stop.\n";
});
// 以下回調(diào)也發(fā)生在Worker進(jìn)程
$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();
原文發(fā)布在 http://www.cnblogs.com/52fhy/p/6931261.html亮垫。