4

The following scripts monitors /dev/shm/test for new files and outputs info about it in real time.

The problem is that when user closes the browser, a inotifywait process remains open, and so on.

Is there any way to avoid this?

<?php
$descriptorspec = array(
  0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
  1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
  2 => array("pipe", "a") // stderr is a file to write to
);

$process = proc_open('inotifywait -mc -e create /dev/shm/test/', $descriptorspec, $pipes);

if (is_resource($process)) {

  header("Content-type: text/html;charset=utf-8;");
  ob_end_flush(); //ends the automatic ob started by PHP
  while ($s = fgets($pipes[1])) {
    print $s;
    flush();
  }
  fclose($pipes[1]);
  fclose($pipes[0]);
  fclose($pipes[2]);

  // It is important that you close any pipes before calling
  // proc_close in order to avoid a deadlock
  $return_value = proc_close($process);

  echo "command returned $return_value\n";
}
?>
4

4 回答 4

1

这是因为inotifywait将等到文件发生更改/dev/shm/test/然后输出标准错误的诊断信息和标准输出的事件信息,并fgets()等到可以读取一行:Reading ends when $length- 1 bytes (2nd parameter) has been read,或换行符(包含在返回值中)或 EOF(以先到者为准)。如果没有指定长度,它将继续从流中读取,直到到达行尾。

所以基本上,你应该从子进程的标准输出管道非阻塞模式中读取数据stream_set_blocking($pipes[1], 0),或者手动检查该管道上是否有数据stream_select()

此外,您需要使用ignore_user_abort(true).

于 2013-10-11T14:24:44.727 回答
1

作为inotifywait基本上永远不会结束的自己的进程运行,您需要向它发送一个 KILL 信号。如果您在 cli 上运行脚本,则 Ctrl+C 信号也会发送到 inotifywait 进程 - 但在网络服务器中运行时您没有。

register_shutdown_function您在由类或类调用的函数中发送信号__destruct

这个简单的 proc_open 包装器可以提供帮助:

class Proc
{
    private $_process;
    private $_pipes;

    public function __construct($cmd, $descriptorspec, $cwd = null, $env = null)
    {
        $this->_process = proc_open($cmd, $descriptorspec, $this->_pipes, $cwd, $env);
        if (!is_resource($this->_process)) {
            throw new Exception("Command failed: $cmd");
        }
    }

    public function __destruct()
    {
        if ($this->isRunning()) {
            $this->terminate();
        }
    }

    public function pipe($nr)
    {
        return $this->_pipes[$nr];
    }

    public function terminate($signal = 15)
    {
        $ret = proc_terminate($this->_process, $signal);
        if (!$ret) {
            throw new Exception("terminate failed");
        }
    }

    public function close()
    {
        return proc_close($this->_process);
    }

    public function getStatus()
    {
        return proc_get_status($this->_process);
    }

    public function isRunning()
    {
        $st = $this->getStatus();
        return $st['running'];
    }
}

$descriptorspec = array(
    0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
    1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
    2 => array("pipe", "a") // stderr is a file to write to
);
$proc = new Proc('inotifywait -mc -e create /dev/shm/test/', $descriptorspec);

header("Content-type: text/html;charset=utf-8;");
ob_end_flush(); //ends the automatic ob started by PHP
$pipe = $proc->pipe(1);
while ($s = fgets($pipe)) {
    print $s;
    flush();
}
fclose($pipe);

$return_value = proc->close($process);

echo "command returned $return_value\n";

或者你可以使用完全相同的 Symfony 进程组件(加上其他有用的东西)

于 2014-12-28T20:30:38.343 回答
0

这有帮助吗?

$proc_info = proc_get_status($process);
pcntl_waitpid($proc_info['pid']);
于 2013-10-08T21:56:34.210 回答
0

您可以使用ignore_user_abort指定当用户关闭浏览器窗口时脚本不应停止执行。这将解决一半的问题,因此您还需要检查窗口是否在循环内关闭,connection_aborted以确定何时需要以有序的方式关闭所有内容:

header("Content-type: text/html;charset=utf-8;");
ignore_user_abort(true);
ob_end_flush(); //ends the automatic ob started by PHP
while ($s = fgets($pipes[1])) {
    print $s;
    flush();
    if (connection_aborted()) {
        proc_terminate($process);
        break;
    }
}
于 2013-09-16T12:47:00.290 回答