PHP高级编程实例:编写守护进程


Posted in PHP onSeptember 02, 2014

1.什么是守护进程

守护进程是脱离于终端并且在后台运行的进程。守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断。

例如 apache, nginx, mysql 都是守护进程

2.为什么开发守护进程

很多程序以服务形式存在,他没有终端或UI交互,它可能采用其他方式与其他程序交互,如TCP/UDP Socket, UNIX Socket, fifo。程序一旦启动便进入后台,直到满足条件他便开始处理任务。

3.何时采用守护进程开发应用程序

以我当前的需求为例,我需要运行一个程序,然后监听某端口,持续接受服务端发起的数据,然后对数据分析处理,再将结果写入到数据库中; 我采用ZeroMQ实现数据收发。

如果我不采用守护进程方式开发该程序,程序一旦运行就会占用当前终端窗框,还有受到当前终端键盘输入影响,有可能程序误退出。

4.守护进程的安全问题

我们希望程序在非超级用户运行,这样一旦由于程序出现漏洞被骇客控制,攻击者只能继承运行权限,而无法获得超级用户权限。

我们希望程序只能运行一个实例,不运行同事开启两个以上的程序,因为会出现端口冲突等等问题。

5.怎样开发守护进程

例 1. 守护进程例示

<?php
class ExampleWorker extends Worker {

 #public function __construct(Logging $logger) {
 # $this->logger = $logger;
 #}

 #protected $logger;
 protected static $dbh;
 public function __construct() {

 }
 public function run(){
  $dbhost = '192.168.2.1';  // 数据库服务器
  $dbport = 3306;
   $dbuser = 'www';  // 数据库用户名
 $dbpass = 'qwer123';    // 数据库密码
  $dbname = 'example';  // 数据库名

  self::$dbh = new PDO("mysql:host=$dbhost;port=$dbport;dbname=$dbname", $dbuser, $dbpass, array(
   /* PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'', */
   PDO::MYSQL_ATTR_COMPRESS => true,
   PDO::ATTR_PERSISTENT => true
   )
  );

 }
 protected function getInstance(){
 return self::$dbh;
  }

}

/* the collectable class implements machinery for Pool::collect */
class Fee extends Stackable {
 public function __construct($msg) {
  $trades = explode(",", $msg);
  $this->data = $trades;
  print_r($trades);
 }

 public function run() {
  #$this->worker->logger->log("%s executing in Thread #%lu", __CLASS__, $this->worker->getThreadId() );

  try {
   $dbh = $this->worker->getInstance();
   
   $insert = "INSERT INTO fee(ticket, login, volume, `status`) VALUES(:ticket, :login, :volume,'N')";
   $sth = $dbh->prepare($insert);
   $sth->bindValue(':ticket', $this->data[0]);
   $sth->bindValue(':login', $this->data[1]);
   $sth->bindValue(':volume', $this->data[2]);
   $sth->execute();
   $sth = null;
   
   /* ...... */
   
   $update = "UPDATE fee SET `status` = 'Y' WHERE ticket = :ticket and `status` = 'N'";
   $sth = $dbh->prepare($update);
   $sth->bindValue(':ticket', $this->data[0]);
   $sth->execute();
   //echo $sth->queryString;
   //$dbh = null;
  }
  catch(PDOException $e) {
   $error = sprintf("%s,%s\n", $mobile, $id );
   file_put_contents("mobile_error.log", $error, FILE_APPEND);
  }
 }
}

class Example {
 /* config */
 const LISTEN = "tcp://192.168.2.15:5555";
 const MAXCONN = 100;
 const pidfile = __CLASS__;
 const uid = 80;
 const gid = 80;
 
 protected $pool = NULL;
 protected $zmq = NULL;
 public function __construct() {
  $this->pidfile = '/var/run/'.self::pidfile.'.pid';
 }
 private function daemon(){
  if (file_exists($this->pidfile)) {
   echo "The file $this->pidfile exists.\n";
   exit();
  }
  
  $pid = pcntl_fork();
  if ($pid == -1) {
    die('could not fork');
  } else if ($pid) {
    // we are the parent
    //pcntl_wait($status); //Protect against Zombie children
   exit($pid);
  } else {
   // we are the child
   file_put_contents($this->pidfile, getmypid());
   posix_setuid(self::uid);
   posix_setgid(self::gid);
   return(getmypid());
  }
 }
 private function start(){
  $pid = $this->daemon();
  $this->pool = new Pool(self::MAXCONN, \ExampleWorker::class, []);
  $this->zmq = new ZMQSocket(new ZMQContext(), ZMQ::SOCKET_REP);
  $this->zmq->bind(self::LISTEN);
  
  /* Loop receiving and echoing back */
  while ($message = $this->zmq->recv()) {
   //print_r($message);
   //if($trades){
     $this->pool->submit(new Fee($message));
     $this->zmq->send('TRUE'); 
   //}else{
   // $this->zmq->send('FALSE'); 
   //}
  }
  $pool->shutdown(); 
 }
 private function stop(){

  if (file_exists($this->pidfile)) {
   $pid = file_get_contents($this->pidfile);
   posix_kill($pid, 9); 
   unlink($this->pidfile);
  }
 }
 private function help($proc){
  printf("%s start | stop | help \n", $proc);
 }
 public function main($argv){
  if(count($argv) < 2){
   printf("please input help parameter\n");
   exit();
  }
  if($argv[1] === 'stop'){
   $this->stop();
  }else if($argv[1] === 'start'){
   $this->start();
  }else{
   $this->help($argv[0]);
  }
 }
}

$cgse = new Example();
$cgse->main($argv);

5.1. 程序启动

下面是程序启动后进入后台的代码

通过进程ID文件来判断,当前进程状态,如果进程ID文件存在表示程序在运行中,通过代码file_exists($this->pidfile)实现,但而后进程被kill需要手工删除该文件才能运行

private function daemon(){
  if (file_exists($this->pidfile)) {
   echo "The file $this->pidfile exists.\n";
   exit();
  }
  
  $pid = pcntl_fork();
  if ($pid == -1) {
    die('could not fork');
  } else if ($pid) {
   // we are the parent
   //pcntl_wait($status); //Protect against Zombie children
   exit($pid);
  } else {
   // we are the child
   file_put_contents($this->pidfile, getmypid());
   posix_setuid(self::uid);
   posix_setgid(self::gid);
   return(getmypid());
  }
 }

程序启动后,父进程会推出,子进程会在后台运行,子进程权限从root切换到指定用户,同时将pid写入进程ID文件。

5.2. 程序停止

程序停止,只需读取pid文件,然后调用posix_kill($pid, 9); 最后将该文件删除。

private function stop(){

  if (file_exists($this->pidfile)) {
   $pid = file_get_contents($this->pidfile);
   posix_kill($pid, 9); 
   unlink($this->pidfile);
  }
 }
PHP 相关文章推荐
PHP中HTTP方式下的Gzip压缩传输方法举偶
Feb 15 PHP
解析php中session的实现原理以及大网站应用应注意的问题
Jun 17 PHP
解析php中获取系统信息的方法
Jun 25 PHP
PHP 使用pcntl和libevent 实现Timer功能
Oct 27 PHP
Smarty局部缓存的几种方法简介
Jun 17 PHP
8个PHP数组面试题
Jun 23 PHP
thinkphp,onethink和thinkox中验证码不显示的解决方法分析
Jun 06 PHP
php操纵mysqli数据库的实现方法
Sep 18 PHP
php rsa 加密,解密,签名,验签详解
Dec 06 PHP
Windows平台实现PHP连接SQL Server2008的方法
Jul 26 PHP
PHP面向对象程序设计之多态性的应用示例
Dec 19 PHP
phpQuery解析HTML乱码问题(补充官网未列出的乱码解决方案)
Apr 01 PHP
php输入流php://input使用浅析
Sep 02 #PHP
php获取URL中带#号等特殊符号参数的解决方法
Sep 02 #PHP
PHP中提问频率最高的11个面试题和答案
Sep 02 #PHP
PHP处理Json字符串解码返回NULL的解决方法
Sep 01 #PHP
PHP实现更新中间关联表数据的两种方法
Sep 01 #PHP
重新认识php array_merge函数
Aug 31 #PHP
浅析PHP中strlen和mb_strlen的区别
Aug 31 #PHP
You might like
其他功能
2006/10/09 PHP
支持中文的php加密解密类代码
2011/11/27 PHP
PHP编码转换函数 自动转换字符集支持数组转换
2012/12/16 PHP
php 模拟 asp.net webFrom 按钮提交事件的思路及代码
2013/12/02 PHP
利用浏览器的Javascript控制台调试PHP程序
2014/01/08 PHP
PHP中UNIX时间戳和日期间的转换与计算实例
2014/11/19 PHP
JavaScript去除空格的三种方法(正则/传参函数/trim)
2013/02/06 Javascript
js中的屏蔽的使用示例
2013/07/30 Javascript
JS+flash实现chrome和ie浏览器下同时可以复制粘贴
2013/09/22 Javascript
JS将数字转换成三位逗号分隔的样式(示例代码)
2014/02/19 Javascript
JavaScript面向对象编程入门教程
2014/04/16 Javascript
jQuery 插件开发指南
2014/11/14 Javascript
Vue和Bootstrap的整合思路详解
2017/06/30 Javascript
jQuery实现可兼容IE6的滚动监听功能
2017/09/20 jQuery
详解vue-meta如何让你更优雅的管理头部标签
2018/01/18 Javascript
nodejs实现一个word文档解析器思路详解
2018/08/14 NodeJs
微信开发之企业付款到银行卡接口开发的示例代码
2018/09/18 Javascript
微信小程序时间选择插件使用详解
2018/12/28 Javascript
jquery实现掷骰子小游戏
2019/10/24 jQuery
vue滑动吸顶及锚点定位的示例代码
2020/05/10 Javascript
vue和小程序项目中使用iconfont的方法
2020/05/19 Javascript
JS实现多功能计算器
2020/10/28 Javascript
简单介绍Python下自己编写web框架的一些要点
2015/04/29 Python
python requests爬取高德地图数据的实例
2018/11/10 Python
对python3中, print横向输出的方法详解
2019/01/28 Python
python+django+rest框架配置创建方法
2019/08/31 Python
Python pandas库中的isnull()详解
2019/12/26 Python
python根据用户需求输入想爬取的内容及页数爬取图片方法详解
2020/08/03 Python
Python __slots__的使用方法
2020/11/15 Python
Hotels.com香港酒店网:你的自由行酒店订房专家
2018/01/22 全球购物
华纳兄弟工作室的官方授权商店:WB Shop
2018/11/30 全球购物
劳动竞赛活动总结
2014/05/05 职场文书
画展观后感
2015/06/17 职场文书
优质服务标语口号
2015/12/26 职场文书
SQL优化老出错,那是你没弄明白MySQL解释计划用法
2021/11/27 MySQL
Oracle中DBLink的详细介绍
2022/04/29 Oracle