隨着互聯網的發展,php快速開發的特點,現在越來越多的團隊將php作爲服務端的編程語言,
大家都知道php是單線程,但使用PCNTL和POSIX等擴展實現多進程編程,相比多線程編程,多進程就容易的多。在使用php開發服務端時,很多時候避免不了和多進程打交道,個人才疏學淺,有疏漏。請望指正。
php創建守護進程
開始之前, 請確認已安裝擴展pcntl和posix。請使用
php -m
創建守護進程就是讓進程脫離終端,獨自在後臺運行,我們可以讓父進程
php 命令行程序實現守護進程化有很多種,在這介紹幾種。
nohup
nohup命令:如果你正在運行一個進程,而且你覺得在退出帳戶時該進程還不會結束,那麼可以使用nohup命令。該命令可以在你退出帳戶/關閉終端之後繼續運行相應的進程。
在缺省情況下該作業的所有輸出都被重定向到一個名爲nohup.out的文件中。
- nohup command > myout.file 2>&1 &
在上面的例子中,0 – stdin (standard input),1 – stdout (standard output),2 – stderr (standard error) ;
2>&1是將標準錯誤(2)重定向到標準輸出(&1),標準輸出(&1)再被重定向輸入到myout.file文件中。
nohup和&的區別
& : 指在後臺運行
nohup : nohup運行命令可以使命令永久的執行下去,和用戶終端沒有關係,例如我們斷開SSH連接都不會影響他的運行,注意了nohup沒有後臺運行的意思;&纔是後臺運行
&是指在後臺運行,但當用戶推出(掛起)的時候,命令自動也跟着退出
結合起來用就是
nohup COMMAND &
這樣就能使命令永久的在後臺執行
例如:
執行命令
nohup yii test-server/start > tets.txt 2>&1 &
查看進程 jobs命令只看當前終端生效的,關閉終端後,在另一個終端jobs已經無法看到後臺跑得程序了,此時利用ps(進程查看命令)
jobs -l
斷開終端,查看進程
ps -ef | grep php
501 5410 1 0 4:08下午 ?? 0:00.05 php /usr/bin/yii test-server/start
501 5888 1 0 4:10下午 ?? 13:27.09 /Applications/PhpStorm.app/Contents/MacOS/phpstorm
501 8723 1 0 4:25下午 ?? 0:00.00 php /usr/bin/yii test-server/start
501 8727 7248 0 4:25下午 ttys002 0:00.00 grep php
這就實現了守護進程.
注意
單獨執行 php myprog.php,當按下ctrl+c時就會中斷程序執行,會kill當前進程以及子進程。
php myprog.php &,這樣執行程序雖然也是轉爲後臺運行,實際上是依賴終端的,當用戶退出終端時進程就會被殺掉。
使用PHP代碼來實現
1、設置守護進程
/**
* 使服務守護進程化.
*
* @return void
*/
protected function deamon()
{
umask(0); // 爲後面的子進程讓出最大權限
$pid = pcntl_fork();
if (-1 == $pid) {
exit("創建子進程失敗" . PHP_EOL);
} elseif ($pid) {
exit();
}
posix_setsid(); // 使當前進程成爲session leader
$pidAgain = pcntl_fork();
if (-1 == $pidAgain) {
exit("再次創建子進程失敗" . PHP_EOL);
} elseif ($pidAgain) {
exit(posix_getpgid(posix_getppid()). PHP_EOL);
}
}
2、處理任務
/**
* 處理請求.
*
* @return void
*/
protected function handleTask()
{
while (true) {
// process task
sleep(2); // 模擬處理請求
//exec('yii rpc-server/rpc-server');
$amqp = yii::$app->params['amqp'];
//建立一個到RabbitMQ服務器的連接
$this->connection = new AMQPStreamConnection($amqp["host"], $amqp["port"], $amqp["user"], $amqp["password"]);
$this->channel = $this->connection->channel();
//接下來,我們創建一個通道
$this->channel->queue_declare('rpc_queue',false,false,false,false);
//回調
$callback = function($req){
$n = intval($req->body);
file_put_contents(date('Ymd').'txt' , $n."\n" , FILE_APPEND | LOCK_EX );
$msg = new AMQPMessage(
(string) $n,
array('correlation_id' => $req->get('correlation_id'))
);
$req->delivery_info['channel']->basic_publish(
$msg,'', $req->get('reply_to')
);
$req->delivery_info['channel']->basic_ack(
$req->delivery_info['delivery_tag']
);
};
$this->channel->basic_qos(null,1,null);
$this->channel->basic_consume('rpc_queue','',false,false,false,false,$callback);
while (count($this->channel->callbacks)) {
$this->channel->wait();
}
$this->channel->close();
$this->connection->close();
}
}
3、合併
/**
* 啓動服務.
*
* @return void
*/
public function actionStart()
{
$this->deamon(); // 守護進程化
$this->handleTask(); // 開始處理任務
}
代碼整合
<?php
/**
* Created by TestServer.php.
* User: gongzhiyang
* Date: 19/6/27
* Time: 5:14 下午
*/
namespace console\controllers;
use yii;
use yii\console\Controller;
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
class TestServerController extends Controller
{
/**
* 啓動服務.
*
* @return void
*/
public function actionStart()
{
$this->deamon(); // 守護進程化
$this->handleTask(); // 開始處理任務
}
/**
* 使服務守護進程化.
*
* @return void
*/
protected function deamon()
{
umask(0); // 爲後面的子進程讓出最大權限
$pid = pcntl_fork();
if (-1 == $pid) {
exit("創建子進程失敗" . PHP_EOL);
} elseif ($pid) {
exit();
}
posix_setsid(); // 使當前進程成爲session leader
$pidAgain = pcntl_fork();
if (-1 == $pidAgain) {
exit("再次創建子進程失敗" . PHP_EOL);
} elseif ($pidAgain) {
exit(posix_getpgid(posix_getppid()). PHP_EOL);
}
}
/**
* 處理請求.
*
* @return void
*/
protected function handleTask()
{
while (true) {
// process task
sleep(2); // 模擬處理請求
//exec('yii rpc-server/rpc-server');
$amqp = yii::$app->params['amqp'];
//建立一個到RabbitMQ服務器的連接
$this->connection = new AMQPStreamConnection($amqp["host"], $amqp["port"], $amqp["user"], $amqp["password"]);
$this->channel = $this->connection->channel();
//接下來,我們創建一個通道
$this->channel->queue_declare('rpc_queue',false,false,false,false);
//回調
$callback = function($req){
$n = intval($req->body);
file_put_contents(date('Ymd').'txt' , $n."\n" , FILE_APPEND | LOCK_EX );
$msg = new AMQPMessage(
(string) $n,
array('correlation_id' => $req->get('correlation_id'))
);
$req->delivery_info['channel']->basic_publish(
$msg,'', $req->get('reply_to')
);
$req->delivery_info['channel']->basic_ack(
$req->delivery_info['delivery_tag']
);
};
$this->channel->basic_qos(null,1,null);
$this->channel->basic_consume('rpc_queue','',false,false,false,false,$callback);
while (count($this->channel->callbacks)) {
$this->channel->wait();
}
$this->channel->close();
$this->connection->close();
}
}
}
啓動
gongzgiyangdeMacBook-Air:~ gongzhiyang$ yii test-server/start
11410
gongzgiyangdeMacBook-Air:~ gongzhiyang$ ps -ef | grep php
501 5410 1 0 4:08下午 ?? 0:00.07 php /usr/bin/yii test-server/start
501 5888 1 0 4:10下午 ?? 18:03.16 /Applications/PhpStorm.app/Contents/MacOS/phpstorm
501 8723 1 0 4:25下午 ?? 0:00.04 php /usr/bin/yii test-server/start
501 11065 1 0 4:37下午 ?? 0:00.98 php /usr/bin/yii rpc-server/rpc-server
501 11414 1 0 4:39下午 ?? 0:00.02 php /usr/bin/yii test-server/start
501 11504 11147 0 4:39下午 ttys003 0:00.01 grep php