我們都知道,PHP有它自帶的程式控制 pcntl
,Swoole
中的 process
提供了更強大的功能,直接擷取了官網的一張圖。
下面我們模擬一個TCP伺服器,演示一下基於 process
的多程式服務。
接下來,先來看我們伺服器的程式碼部分.我們設定子程式數為3個,在下面這段程式碼中,主程式啟動之後,會額外啟動3個子程式,負責處理客戶端連線以及請求操作,當子程式退出後,主程式會重新建立新的子程式.如果主程式退出,那麼子程式在處理完當前請求之後也會退出。
:pencil2:Server
<?php
class Server
{
private $mpid; //主程式id
private $pids = []; //子程式陣列
private $socket; //網路套間字
const Max_PROCESS = 3; //最大建立程式數
//主程式邏輯
public function run()
{
$process = new \Swoole\Process(function () {
//獲取主程式id
$this->mpid = posix_getpid();
echo time() . " master process pid: {$this->mpid}" . PHP_EOL;
//建立TCP伺服器並獲取套間字
$this->socket = stream_socket_server("tcp://127.0.0.1:9505", $errno, $errstr);
if (!$this->socket) {
exit("service start error:$errstr --- $errno");
}
//啟動子程式
for ($i = 1; $i <= self::Max_PROCESS; $i++) {
$this->startWorkerProcess();
}
echo "Waiting client connect" . PHP_EOL;
//主程式等待子程式退出 必須是死迴圈
while (1) {
if(count($this->pids)){
//回收結束執行的子程式,預設為阻塞,false 表示非阻塞模式,如果失敗返回 false
$ret = \Swoole\Process::wait(false);
if ($ret) {
echo time() . " worker process: {$ret['pid']} exit ,then new process..." . PHP_EOL;
//新建立一個子程式
$this->startWorkerProcess();
//從陣列中刪除這個已不存在的子程式 pid
$index=array_search($ret['pid'],$this->pids);
unset($this->pids[$index]);
}
}
sleep(1); //
}
}, false, false);
//把當前程式升級為守護程式
\Swoole\Process::daemon();
$process->start();
}
//建立子程式
public function startWorkerProcess()
{
$process = new \Swoole\Process(function (\Swoole\Process $work) {
$this->acceptClient($work);
}, false, false);
$pid = $process->start();
$this->pids[] = $pid;
}
//接收客戶端請求
public function acceptClient(&$worker)
{
//子程式等待客戶端連線,不能退出
while (1) {
//接收由 stream_socket_server()建立的的套間字連線
$conn = stream_socket_accept($this->socket, -1);
//如果定義了連線回撥的地址,就呼叫
if ($this->onConnect) {
call_user_func($this->onConnect, $conn);
}
//開始迴圈讀取客戶端資訊
$recv = ''; //實際接收資料
$buffer = ''; //緩衝資料
while (1) {
//檢查主程式是否正常,如果不正常,退出子程式
$this->checkmPid($worker);
//讀取客戶端資訊
$buffer = fread($conn, 20);
//如果沒有訊息
if ($buffer === false || $buffer === '') {
//如果設定了了解關閉的回撥函式,那麼執行關閉回撥
if ($this->onClose) {
call_user_func($this->onClose, $conn);
}
//等待下一個連線訊息
break;
}
//訊息結束的位置
$pos = strpos($buffer, "\n");
//沒有結束符,說明還沒有讀完
if (false === $pos) {
$recv .= $buffer; //拼接訊息
} else { //訊息讀完了,處理訊息
$recv .= trim(substr($buffer, 0, $pos + 1));
//如果定義了處理訊息回撥的函式,那就直接呼叫回撥
if ($this->onMessage) {
call_user_func($this->onMessage, $conn, $recv);
}
//如果接收到quit 表示退出,那麼關閉這個連結,等待下一個客戶端連線
if ($recv === 'quit') {
echo 'client close' . PHP_EOL;
fclose($conn);
break;
}
}
//清空訊息
$recv = '';
}
}
}
public function checkmPid(&$worker)
{
//說明主程式不存在,子程式此時是殭屍程式,需要退出
if (!\Swoole\Process::kill($this->mpid, 0)) {
$worker->exit();
echo "worker: {$worker->pid} exit" . PHP_EOL;
}
}
}
$server = new Server();
//連線回撥
$server->onConnect = function ($conn) {
echo "onConnect -- accepted " . stream_socket_get_name($conn, true) . PHP_EOL;
};
//接收訊息回撥
$server->onMessage = function ($conn, $msg) {
echo "message is ------ $msg" . PHP_EOL;
fwrite($conn, "received: " . $msg . "\n");
};
//關閉回撥
$server->onClose = function ($conn) {
echo "close---" . stream_socket_get_name($conn, true) . PHP_EOL;
};
$server->run();
開啟服務之後,你可以通過命令檢視程式 pstree -p 主程式id
或者通過命令檢視執行的更多資訊 ps -ef | grep Server.php
,可以看到對應程式的 id。
現在讓我們建立一個簡單的客戶端連線伺服器。
:pencil2:Client
<?php
go(function () {
$client = new \Swoole\Coroutine\Client(SWOOLE_SOCK_TCP);
// 嘗試與指定 TCP 服務端建立連線(IP和埠號需要與服務端保持一致,超時時間為0.5秒)
if ($client->connect("127.0.0.1", 9505, 0.5)) {
// 建立連線後傳送內容
$client->send("hello world\n");
// 列印接收到的訊息
echo $client->recv().PHP_EOL;
sleep(2);
// 關閉連線
$client->close();
} else {
echo "connect failed.";
}
});
建立一個協程客戶端請求,服務端接收資料,服務端響應資料,客戶端接收資料的整個過程。整理客戶端 sleep
兩秒之後才關閉連線。
然後我們來很粗糙的手動kill掉其中的一個子程式。父程式又重新建立一個子程式。
和預期一樣,主程式又重新建立了一個新的子程式。