|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
php manual(PHP手册)肯定是要从网上下载一个的,它很权威,也很全面,我自己认为它是一本很好的参考书,但是不适合新手当教材使用。 摘要:在本文中,让咱们配合切磋基于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;
}
/*胜利锁定以平安地履行任务*/
注重,有关锁机制的会商触及较多内容,在此不多加注释。
<P> 四. 构建监督办事
在这一节中,咱们将利用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;
}
}
}
与你之前构建的框架比拟,这个办事极为复杂,在此恕不多描写。
<P> 五. 示例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('oncall@example.com', 'Service Event',$message);
if($service->consecutive_failures() > 5) {
mail('oncall_backup@example.com', '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。我是根据自己的成长历程来写的,如有不对的还请指正。 |
|