国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發 > PHP > 正文

用PHP構建一個簡易監視引擎

2024-05-04 23:03:51
字體:
來源:轉載
供稿:網友
  摘要在本文中,讓我們共同探討基于php語言構建一個基本的服務器端監視引擎的諸多技巧及注意事項,并給出完整的源碼實現。

  一. 更改工作目錄的問題

  當你編寫一個監視程序時,讓它設置自己的工作目錄通常更好些。這樣以來,如果你使用一個相對路徑讀寫文件,那么,它會根據情況自動處理用戶期望存放文件的位置。總是限制程序中使用的路徑盡管是一種良好的實踐;但是,卻失去了應有的靈活性。因此,改變你的工作目錄的最安全的方法是,既使用chdir()也使用chroot()。

  chroot()可用于php的cli和cgi版本中,但是卻要求程序以根權限運行。chroot()實際上把當前進程的路徑從根目錄改變到指定的目錄。這使得當前進程只能執行存在于該目錄下的文件。經常情況下,chroot()由服務器作為一個"安全設備"使用以確保惡意代碼不會修改一個特定的目錄之外的文件。請牢記,盡管chroot()能夠阻止你訪問你的新目錄之外的任何文件,但是,任何當前打開的文件資源仍然能夠被存取。例如,下列代碼能夠打開一個日志文件,調用chroot()并切換到一個數據目錄;然后,仍然能夠成功地登錄并進而打開文件資源:

<?php
$logfile = fopen("/var/log/chroot.log", "w");
chroot("/users/george");
fputs($logfile, "hello from inside the chroot/n");
?>

  如果一個應用程序不能使用chroot(),那么你可以調用chdir()來設置工作目錄。例如,當代碼需要加載特定的代碼(這些代碼能夠在系統的任何地方被定位時),這是很有用的。注意,chdir()沒有提供安全機制來防止打開未授權的文件。

  二. 放棄特權

  當編寫unix守護程序時,一種經典的安全預防措施是讓它們放棄所有不需要的特權;否則,擁有不需要的特權容易招致不必要的麻煩。在代碼(或php本身)中含有漏洞的情況下,通過確保一個守護程序以最小權限用戶身份運行,往往能夠使損失減到最小。

  一種實現此目的的方法是,以非特權用戶身份執行該守護程序。然而,如果程序需要在一開始就打開非特權用戶無權打開的資源(例如日志文件,數據文件,套接字,等等)的話,這通常是不夠的。

  如果你以根用戶身份運行,那么你能夠借助于posix_setuid()和posiz_setgid()函數來放棄你的特權。下面的示例把當前運行程序的特權改變為用戶nobody所擁有的那些權限:

$pw=posix_getpwnam('nobody');
posix_setuid($pw['uid']);
posix_setgid($pw['gid']);

  就象chroot()一樣,任何在放棄特權之前被打開的特權資源都會保持為打開,但是不能創建新的資源。

  三. 保證排它性

  你可能經常想實現:一個腳本在任何時刻僅運行一個實例。為了保護腳本,這是特別重要的,因為在后臺運行容易導致偶然情況下調用多個實例。

  保證這種排它性的標準技術是,通過使用flock()來讓腳本鎖定一個特定的文件(經常是一個加鎖文件,并且被排它式使用)。如果鎖定失敗,該腳本應該輸出一個錯誤并退出。下面是一個示例:

$fp=fopen("/tmp/.lockfile","a");
if(!$fp || !flock($fp, lock_ex | lock_nb)) {
 fputs(stderr, "failed to acquire lock/n");
 exit;
}
/*成功鎖定以安全地執行工作*/


  注意,有關鎖機制的討論涉及較多內容,在此不多加解釋。

  四. 構建監視服務

  在這一節中,我們將使用php來編寫一個基本的監視引擎。因為你不會事先知道怎樣改變,所以你應該使它的實現既靈活又具可能性。
該記錄程序應該能夠支持任意的服務檢查(例如,http和ftp服務)并且能夠以任意方式(通過電子郵件,輸出到一個日志文件,等等)記錄事件。你當然想讓它以一個守護程序方式運行;所以,你應該請求它輸出其完整的當前狀態。

  一個服務需要實現下列抽象類:

abstract class servicecheck {
 const failure = 0;
 const success = 1;
 protected $timeout = 30;
 protected $next_attempt;
 protected $current_status = servicecheck::success;
 protected $previous_status = servicecheck::success;
 protected $frequency = 30;
 protected $description;
 protected $consecutive_failures = 0;
 protected $status_time;
 protected $failure_time;
 protected $loggers = array();
 abstract public function __construct($params);
 public function __call($name, $args)
 {
  if(isset($this->$name)) {
   return $this->$name;
  }
 }
 public function set_next_attempt()
 {
  $this->next_attempt = time() + $this->frequency;
 }
 public abstract function run();
 public function post_run($status)
 {
  if($status !== $this->current_status) {
   $this->previous_status = $this->current_status;
  }
  if($status === self::failure) {
   if( $this->current_status === self::failure ) {
    $this->consecutive_failures++;
   }
   else {
    $this->failure_time = time();
   }
  }
  else {
   $this->consecutive_failures = 0;
  }
  $this->status_time = time();
  $this->current_status = $status;
  $this->log_service_event();
 }
 public function log_current_status()
 {
  foreach($this->loggers as $logger) {
   $logger->log_current_status($this);
  }
 }
 private function log_service_event()
 {
  foreach($this->loggers as $logger) {
   $logger->log_service_event($this);
  }
 }
 public function register_logger(servicelogger $logger)
 {
  $this->loggers[] = $logger;
 }
}

  上面的__call()重載方法提供對一個servicecheck對象的參數的只讀存取操作:

  · timeout-在引擎終止檢查之前,這一檢查能夠掛起多長時間。

  · next_attempt-下次嘗試連接到服務器的時間。

  · current_status-服務的當前狀態:success或failure。

  · previous_status-當前狀態之前的狀態。

  · frequency-每隔多長時間檢查一次服務。

  · description-服務描述。

  · consecutive_failures-自從上次成功以來,服務檢查連續失敗的次數。

  · status_time-服務被檢查的最后時間。

  · failure_time-如果狀態為failed,則它代表發生失敗的時間。

  這個類還實現了觀察者模式,允許servicelogger類型的對象注冊自身,然后當調用log_current_status()或log_service_event()時調用它。

  這里實現的關鍵函數是run(),它負責定義應該怎樣執行檢查。如果檢查成功,它應該返回success;否則返回failure。

  當定義在run()中的服務檢查返回后,post_run()方法被調用。它負責設置對象的狀態并實現記入日志。

  servicelogger接口:指定一個日志類僅需要實現兩個方法:log_service_event()和log_current_status(),它們分別在當一個run()檢查返回時和當實現一個普通狀態請求時被調用。

  該接口如下所示:

interface servicelogger {
 public function log_service_event(servicecheck$service);
 public function log_current_status(servicecheck$service);
}

  最后,你需要編寫引擎本身。該想法類似于在前一節編寫簡單程序時使用的思想:服務器應該創建一個新的進程來處理每一次檢查并使用一個sigchld處理器來檢測當檢查完成時的返回值。可以同時檢查的最大數目應該是可配置的,從而可以防止對系統資源的過渡使用。所有的服務和日志都將在一個xml文件中定義。

  下面是定義該引擎的servicecheckrunner類:

class servicecheckrunner {
 private $num_children;
 private $services = array();
 private $children = array();
 public function _ _construct($conf, $num_children)
 {
  $loggers = array();
  $this->num_children = $num_children;
  $conf = simplexml_load_file($conf);
  foreach($conf->loggers->logger as $logger) {
   $class = new reflection_class("$logger->class");
   if($class->isinstantiable()) {
    $loggers["$logger->id"] = $class->newinstance();
   }
   else {
    fputs(stderr, "{$logger->class} cannot be instantiated./n");
    exit;
   }
  }
  foreach($conf->services->service as $service) {
   $class = new reflection_class("$service->class");
   if($class->isinstantiable()) {
    $item = $class->newinstance($service->params);
    foreach($service->loggers->logger as $logger) {
     $item->register_logger($loggers["$logger"]);
    }
    $this->services[] = $item;
   }
   else {
    fputs(stderr, "{$service->class} is not instantiable./n");
    exit;
   }
  }
 }
 private function next_attempt_sort($a, $b){
  if($a->next_attempt() == $b->next_attempt()) {
   return 0;
  }
  return ($a->next_attempt() < $b->next_attempt())? -1 : 1;
 }
 private function next(){
  usort($this->services,array($this,'next_attempt_sort'));
  return $this->services[0];
 }
 public function loop(){
  declare(ticks=1);
  pcntl_signal(sigchld, array($this, "sig_child"));
  pcntl_signal(sigusr1, array($this, "sig_usr1"));
  while(1) {
   $now = time();
   if(count($this->children)< $this->num_children) {
    $service = $this->next();
    if($now < $service->next_attempt()) {
     sleep(1);
     continue;
    }
    $service->set_next_attempt();
    if($pid = pcntl_fork()) {
     $this->children[$pid] = $service;
    }
    else {
     pcntl_alarm($service->timeout());
     exit($service->run());
    }
   }
  }
 }
 public function log_current_status(){
  foreach($this->services as $service) {
   $service->log_current_status();
  }
 }
 private function sig_child($signal){
  $status = servicecheck::failure;
  pcntl_signal(sigchld, array($this, "sig_child"));
  while(($pid = pcntl_wait($status, wnohang)) > 0){
   $service = $this->children[$pid];
   unset($this->children[$pid]);
   if(pcntl_wifexited($status) && pcntl_wexitstatus($status) ==servicecheck::success)
   {
    $status = servicecheck::success;
   }
   $service->post_run($status);
  }
 }
 private function sig_usr1($signal){
  pcntl_signal(sigusr1, array($this, "sig_usr1"));
  $this->log_current_status();
 }
}

  這是一個很復雜的類。其構造器讀取并分析一個xml文件,創建所有的將被監視的服務,并創建記錄它們的日志程序。

  loop()方法是該類中的主要方法。它設置請求的信號處理器并檢查是否能夠創建一個新的子進程。現在,如果下一個事件(以next_attempt時間chuo排序)運行良好,那么一個新的進程將被創建。在這個新的子進程內,發出一個警告以防止測試持續時間超出它的時限,然后執行由run()定義的測試。

  還存在兩個信號處理器:sigchld處理器sig_child(),負責收集已終止的子進程并執行它們的服務的post_run()方法;sigusr1處理器sig_usr1(),簡單地調用所有已注冊的日志程序的log_current_status()方法,這可以用于得到整個系統的當前狀態。

  當然,這個監視架構并不沒有做任何實際的事情。但是首先,你需要檢查一個服務。下列這個類檢查是否你從一個http服務器取回一個"200 server ok"響應:

class http_servicecheck extends servicecheck{
 public $url;
 public function _ _construct($params){
  foreach($params as $k => $v) {
   $k = "$k";
   $this->$k = "$v";
  }
 }
 public function run(){
  if(is_resource(@fopen($this->url, "r"))) {
   return servicecheck::success;
  }
  else {
   return servicecheck::failure;
  }
 }
}

  與你以前構建的框架相比,這個服務極其簡單,在此恕不多描述。

  五. 示例servicelogger進程

  下面是一個示例servicelogger進程。當一個服務停用時,它負責把一個電子郵件發送給一個待命人員:

class emailme_servicelogger implements servicelogger {
 public function log_service_event(servicecheck$service)
 {
  if($service->current_status ==servicecheck::failure) {
   $message = "problem with{$service->description()}/r/n";
   mail('[email protected]', 'service event',$message);
   if($service->consecutive_failures() > 5) {
    mail('[email protected]', 'service event', $message);
   }
  }
 }
 public function log_current_status(servicecheck$service){
  return;
 }
}

  如果連續失敗五次,那么該進程還把一個消息發送到一個備份地址。注意,它并沒有實現一個有意義的log_current_status()方法。

  無論何時象如下這樣改變一個服務的狀態,你都應該實現一個寫向php錯誤日志的servicelogger進程:

class errorlog_servicelogger implements servicelogger {
 public function log_service_event(servicecheck$service)
 {
  if($service->current_status() !==$service->previous_status()) {
   if($service->current_status() ===servicecheck::failure) {
    $status = 'down';
   }
   else {
    $status = 'up';
   }
   error_log("{$service->description()} changed status to $status");
  }
 }
 public function log_current_status(servicecheck$service)
 {
  error_log("{$service->description()}: $status");
 }
}

  該log_current_status()方法意味著,如果進程發送一個sigusr1信號,它將把其完整的當前狀態復制到你的php錯誤日志中。
  
  該引擎使用如下的一個配置文件:

<config>
 <loggers>
  <logger>
   <id>errorlog</id>
   <class>errorlog_servicelogger</class>
  </logger>
  <logger>
   <id>emailme</id>
   <class>emailme_servicelogger</class>
  </logger>
 </loggers>
 <services>
  <service>
   <class>http_servicecheck</class>
   <params>
    <description>omniti http check</description>
    <url>http://www.omniti.com</url>
    <timeout>30</timeout>
    <frequency>900</frequency>
   </params>
   <loggers>
    <logger>errorlog</logger>
    <logger>emailme</logger>
   </loggers>
  </service>
 <service>
 <class>http_servicecheck</class>
 <params>
  <description>home page http check</description>
  <url>http://www.schlossnagle.org/~george</url>
  <timeout>30</timeout>
  <frequency>3600</frequency>
 </params>
 <loggers>
  <logger>errorlog</logger>
 </loggers>
</service>
</services>
</config>

  當傳遞這個xml文件時,servicecheckrunner的構造器對于每一個指定的日志實例化一個日志記錄程序。然后,它相應于每一個指定的服務實例化一個servicecheck對象。

  注意 該構造器使用reflection_class類來實現該服務和日志類的內在檢查-在你試圖實例化它們之前。盡管這是不必要的,但是它很好地演示了php 5中新的反射(reflection)api的使用。除了這些類以外,反射api還提供一些類來實現對php中幾乎任何內部實體(類,方法或函數)的內在檢查。

  為了使用你構建的引擎,你仍然需要一些包裝代碼。監視程序應該會禁止你試圖兩次啟動它-你不需要對每一個事件建立兩份消息。當然,該監視程序還應該接收包括下列選項在內的一些選項:

選項描述
[-f]引擎的配置文件的一個位置,默認是monitor.xml。
[-n] 引擎允許的子進程池的大小,默認是5。
[-d] 一個停用該引擎的守護功能的標志。在你編寫一個把信息輸出到stdout或stderr的調試servicelogger進程時,這是很有用的。

  下面是最終的監視程序腳本,它分析選項,保證排它性并且運行服務檢查:

require_once "service.inc";
require_once "console/getopt.php";
$shortoptions = "n:f:d";
$default_opts = array('n' => 5, 'f' =>'monitor.xml');
$args = getoptions($default_opts, $shortoptions,null);
$fp = fopen("/tmp/.lockfile", "a");
if(!$fp || !flock($fp, lock_ex | lock_nb)) {
 fputs($stderr, "failed to acquire lock/n");
 exit;
}
if(!$args['d']) {
 if(pcntl_fork()) {
  exit;
 }
 posix_setsid();
 if(pcntl_fork()) {
  exit;
 }
}
fwrite($fp, getmypid());
fflush($fp);
$engine = new servicecheckrunner($args['f'],$args['n']);
$engine->loop();

  注意,這個示例使用了定制的getoptions()函數。

  在編寫一個適當的配置文件后,你可以按如下方式啟動該腳本:

  > ./monitor.php -f /etc/monitor.xml

  這可以保護并繼續監視直到機器被關掉或該腳本被殺死。

  這個腳本相當復雜,但是仍然存在一些容易改進的地方,這些只好留給讀者作為練習之用:

  · 添加一個重新分析配置文件的sighup處理器以便你能夠在不啟動服務器的情況下改變配置。

  · 編寫一個能夠登錄到一個數據庫的servicelogger以用于存儲查詢數據。

  · 編寫一個web前端程序以為整個監視系統提供一種良好的gui。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 长白| 正定县| 广德县| 涪陵区| 拉萨市| 湘乡市| 宣化县| 玉溪市| 德化县| 长葛市| 香河县| 报价| 西吉县| 大名县| 鄂托克旗| 庐江县| 荆州市| 隆安县| 成都市| 泰和县| 无锡市| 丘北县| 乌拉特前旗| 襄汾县| 太康县| 福泉市| 明星| 尖扎县| 墨竹工卡县| 都兰县| 台湾省| 富宁县| 九江市| 晋城| 雷波县| 团风县| 蕉岭县| 鹿邑县| 西峡县| 黄冈市| 平南县|