2016-01-21 55 views
1

我不是PHP的专家。我有一个使用EXEC运行WINRS的函数,然后在远程服务器上运行命令。问题是这个函数被放入一个调用getservicestatus函数几十次的循环中。有时WINRS命令会卡住或花费比预期更长的时间,导致PHP脚本超时并抛出500错误。PHP函数超时

暂时我已经降低了PHP中的设置超时值,并在IIS中创建了一个自定义的500页,如果引用页面等于脚本名称,然后重新加载页面(否则抛出错误)。但这很混乱。显然它不适用于每次调用该函数,因为它是全局的。所以它只会避免页面停止在HTTP 500错误。

我真的很想做的就是在函数本身上设置5秒的超时时间。我一直在搜索相当多的东西,甚至在stackoverflow上都找不到答案。是的,也有类似的问题,但我一直没有找到任何与我的功能相关的问题。也许有一种方法可以在执行命令时执行此操作,例如exec()的替代方法?我不知道。理想情况下,我希望该功能在5秒后超时并将$ servicestate返回为0.

代码被注释以解释我的意大利面条混乱。我很遗憾,你必须看到它......

function getservicestatus($servername, $servicename, $username, $password) 
{ 
//define start so that if an invalid result is reached the function can be restarted using goto. 
start: 

//Define command to use to get service status. 
$command = 'winrs /r:' . $servername . ' /u:' . $username . ' /p:' . $password . ' sc query ' . $servicename . ' 2>&1'; 
exec($command, $output); 

//Defines the server status as $servicestate which is stored in the fourth part of the command array. 
//Then the string "STATE" and any number is stripped from $servicestate. This will leave only the status of the service (e.g. RUNNING or STOPPED). 
$servicestate = $output[3]; 
$strremove = array('/STATE/','/:/','/[0-9]+/','/\s+/'); 
$servicestate = preg_replace($strremove, '', $servicestate); 

//Define an invalid output. Sometimes the array is invalid. Catch this issue and restart the function for valid output. 
//Typically this can be caught when the string "SERVICE_NAME" is found in $output[3]. 
$badservicestate = "SERVICE_NAME" . $servicename; 
if($servicestate == $badservicestate) { 
    goto start; 
} 

//Service status (e.g. Running, Stopped Disabled) is returned as $servicestate. 
return $servicestate; 
} 
+0

我相信你不能有'开始'那样的?你必须记得这个功能。 – Script47

+0

可能。但是像这样实施它实际上解决了我遇到的问题之一。基本上,winrs命令的输出有时可能是无效的。我用$ badservicestate捕获并启动;。有用。但这并不意味着它是正确的。 – Cormang

+0

问题在于exec()将会阻塞,直到命令完成,除非你调用的外部命令有一个自己的超时方法,否则你将不得不使用['proc_open()'' ](http://php.net/manual/en/function.proc-open.php)。 – Sammitch

回答

0

最简单的解决方案,因为你是调用外部的过程,你真正需要它的输出在你的脚本,来讲是重写execproc_open和非阻塞I/O:

function exec_timeout($cmd, $timeout, &$output = '') { 
    $fdSpec = [ 
     0 => ['file', '/dev/null', 'r'], //nothing to send to child process 
     1 => ['pipe', 'w'], //child process's stdout 
     2 => ['file', '/dev/null', 'a'], //don't care about child process stderr 
    ]; 
    $pipes = []; 
    $proc = proc_open($cmd, $fdSpec, $pipes); 
    stream_set_blocking($pipes[1], false); 

    $stop = time() + $timeout; 
    while(1) { 
     $in = [$pipes[1]]; 
     $out = []; 
     $err = []; 
     stream_select($in, $out, $err, min(1, $stop - time())); 

     if($in) { 
      while(!feof($in[0])) { 
       $output .= stream_get_contents($in[0]); 
       break; 
      } 

      if(feof($in[0])) { 
       break; 
      } 
     } else if($stop <= time()) { 
      break; 
     } 
    } 

    fclose($pipes[1]); //close process's stdout, since we're done with it 
    $status = proc_get_status($proc); 
    if($status['running']) { 
     proc_terminate($proc); //terminate, since close will block until the process exits itself 

     return -1; 
    } else { 
     proc_close($proc); 

     return $status['exitcode']; 
    } 
} 

$returnValue = exec_timeout('YOUR COMMAND HERE', $timeout, $output); 

此代码:

  • 使用proc_open打开一个子进程。我们只为孩子的stdout指定管道,因为我们没有任何要发送给它的管道,并且不关心其stderr输出。如果你这样做,你必须相应地调整下面的代码。
  • stream_select()上的循环,这将阻止设置为$timeout$stop - time())。
  • 如果有输入,将输入缓冲区的内容为var_dump()。这不会阻塞,因为我们在管道上有stream_set_blocking($pipe[1], false)。您可能希望将内容保存到变量中(附加它而不是覆盖它),而不是打印出来。
  • 当我们读完整个文件,或者超出了我们的超时时间时,请停止。
  • 通过关闭我们打开的进程来清理。
  • 输出存储在传递引用字符串$output中。返回进程的退出代码,或者在超时的情况下返回-1
+0

我最终做了类似的事情,但这样做比较好。谢谢! – Cormang