proc_open 尝试从流中读取时挂起
proc_open hangs when trying to read from a stream
我在 Windows 上尝试使用 ffmpeg
转换 wmv 文件(到 flv)时遇到了 proc_open
的问题,但我怀疑我会遇到每当某些情况发生时,都会出现相同的情况。
基本上我的代码如下:
$descriptorspec = array
(
array("pipe", "r"),
array("pipe", "w"),
array("pipe", "w")
);
$pipes = array();
$procedure = proc_open('cd "C:/Program Files/ffmpeg/bin" && "ffmpeg.exe" -i "C:/wamp/www/project/Wildlife.wmv" -deinterlace -qdiff 2 -ar 22050 "C:/wamp/www/project/Wildlife.flv"', $descriptorspec, $pipes);
var_dump(stream_get_contents($pipes[1]));
现在,此代码将导致 PHP 无限期挂起(我将使用 fgets
或 stream_select
而不是 stream_get_contents
并不重要,行为是一致的)。
它的原因(我怀疑)是,当 STDOUT 流成功打开时,进程不会向它写入任何内容(即使 运行在 cmd 中使用相同的命令显示输出)并且因此,尝试从此类流中读取数据会导致与 here 所述相同的问题,因此 - PHP 等待流中包含任何内容,进程不会向其中写入任何内容。
然而(额外的乐趣),设置stream_set_timeout
或stream_set_blocking
没有任何效果。
因此 - 有人可以 confirm/deny 了解正在发生的事情,并且如果可能的话,说明我该如何应对这种情况?我查看了 PHP 个错误,所有 proc_open hangs
个错误似乎都已修复。
暂时我实现了这样的解决方案:
$timeout = 60;
while (true) {
sleep(1);
$status = proc_get_status($procedure);
if (!$status['running'] || $timeout == 0) break;
$timeout--;
}
但是,我真的不想依赖这样的东西:
- 我将拥有 运行 超过一分钟的进程 - 此类进程将被错误地报告为上述类型
- 我想知道 ffmpeg 何时完成视频转换 - 目前我只知道该过程在一分钟后仍在 运行ning,我无法真正做任何事情来检查是否有任何输出(因为它会挂起 PHP)。
此外,我真的不想等待一整分钟来检查进程(例如 - 从命令行转换给定视频需要 <10 秒),我会制作需要更多时间的视频待转换。
根据@Sjon 的评论,这是我正在使用的 stream_select
,由于相同的问题而阻塞 - STDOUT 未写入:
$descriptorspec = array
(
array("pipe", "r"),
array("pipe", "w"),
array("pipe", "w")
);
$pipes = array();
$procedure = proc_open('cd "C:/Program Files/ffmpeg/bin" && "ffmpeg.exe" -i "C:/wamp/www/sandbox/Wildlife.wmv" -deinterlace -qdiff 2 -ar 22050 "C:/wamp/www/sandbox/Wildlife.flv"', $descriptorspec, $pipes);
$read = array($pipes[0]);
$write = array($pipes[1], $pipes[2]);
$except = array();
while(true)
if(($num_changed_streams = stream_select($read, $write, $except, 10)) !== false)
{
foreach($write as $stream)
var_dump(stream_get_contents($stream));
exit;
}
else
break;
每次与@Sjon 的对话 - 从 Windows 上的缓冲流读取被破坏。最后的解决方案是通过 shell 使用流重定向,然后读取创建的文件 - 如此
$descriptorspec = array
(
array("pipe", "r"),
array("pipe", "w"),
array("pipe", "w")
);
$pipes = array();
$procedure = proc_open('cd "C:/Program Files/ffmpeg/bin" && "ffmpeg.exe" -i "C:/wamp/www/sandbox/Wildlife.mp4" -deinterlace -qdiff 2 -ar 22050 "C:/wamp/www/sandbox/Wildlife.flv" > C:/stdout.log 2> C:/stderr.log', $descriptorspec, $pipes);
proc_close($procedure);
$output = file_get_contents("C:/stdout.log");
$error = file_get_contents("C:/stderr.log");
unlink("C:/stdout.log");
unlink("C:/stderr.log");
由于流是缓冲的,在文件中我们将得到未缓冲的输出(我也想要这样)。而且我们不需要检查文件是否更改,因为 shell 的结果是无缓冲和同步的。
这需要一些时间来重现,但我发现了你的问题。您 运行 的命令会在您 运行 时输出一些诊断信息;但它不输出到 stdout,而是输出到 stderr。原因在 man stderr
:
中有解释
Under normal circumstances every UNIX program has three streams opened for it when it starts up, one for input, one for output, and one for printing diagnostic or error messages
如果你愿意properly use streams;这不是问题;但是您改为调用 stream_get_contents($pipes[1])
。这导致 PHP 等待来自 stdout 的输出,而该输出永远不会到达。这个修复很简单;改为从 stderr stream_get_contents($pipes[2])
读取,脚本将在进程结束后立即退出
扩展您在问题中添加的 stream_select; stream_select 未在 php 中的 windows 上实现,手册中是这样说的:
Use of stream_select() on file descriptors returned by proc_open() will fail and return FALSE under Windows.
因此,如果上面发布的代码不起作用;我不确定会发生什么。您是否考虑过放弃您的流解决方案,转而使用简单的 exec() 调用?如果您将 >%TEMP%/out.log 2>%TEMP%/err.log
附加到您的命令,您仍然可以从该过程中读取输出并且它可能会更快完成(无需等待不可修改的超时)
我在 Windows 上尝试使用 ffmpeg
转换 wmv 文件(到 flv)时遇到了 proc_open
的问题,但我怀疑我会遇到每当某些情况发生时,都会出现相同的情况。
基本上我的代码如下:
$descriptorspec = array
(
array("pipe", "r"),
array("pipe", "w"),
array("pipe", "w")
);
$pipes = array();
$procedure = proc_open('cd "C:/Program Files/ffmpeg/bin" && "ffmpeg.exe" -i "C:/wamp/www/project/Wildlife.wmv" -deinterlace -qdiff 2 -ar 22050 "C:/wamp/www/project/Wildlife.flv"', $descriptorspec, $pipes);
var_dump(stream_get_contents($pipes[1]));
现在,此代码将导致 PHP 无限期挂起(我将使用 fgets
或 stream_select
而不是 stream_get_contents
并不重要,行为是一致的)。
它的原因(我怀疑)是,当 STDOUT 流成功打开时,进程不会向它写入任何内容(即使 运行在 cmd 中使用相同的命令显示输出)并且因此,尝试从此类流中读取数据会导致与 here 所述相同的问题,因此 - PHP 等待流中包含任何内容,进程不会向其中写入任何内容。
然而(额外的乐趣),设置stream_set_timeout
或stream_set_blocking
没有任何效果。
因此 - 有人可以 confirm/deny 了解正在发生的事情,并且如果可能的话,说明我该如何应对这种情况?我查看了 PHP 个错误,所有 proc_open hangs
个错误似乎都已修复。
暂时我实现了这样的解决方案:
$timeout = 60;
while (true) {
sleep(1);
$status = proc_get_status($procedure);
if (!$status['running'] || $timeout == 0) break;
$timeout--;
}
但是,我真的不想依赖这样的东西:
- 我将拥有 运行 超过一分钟的进程 - 此类进程将被错误地报告为上述类型
- 我想知道 ffmpeg 何时完成视频转换 - 目前我只知道该过程在一分钟后仍在 运行ning,我无法真正做任何事情来检查是否有任何输出(因为它会挂起 PHP)。
此外,我真的不想等待一整分钟来检查进程(例如 - 从命令行转换给定视频需要 <10 秒),我会制作需要更多时间的视频待转换。
根据@Sjon 的评论,这是我正在使用的 stream_select
,由于相同的问题而阻塞 - STDOUT 未写入:
$descriptorspec = array
(
array("pipe", "r"),
array("pipe", "w"),
array("pipe", "w")
);
$pipes = array();
$procedure = proc_open('cd "C:/Program Files/ffmpeg/bin" && "ffmpeg.exe" -i "C:/wamp/www/sandbox/Wildlife.wmv" -deinterlace -qdiff 2 -ar 22050 "C:/wamp/www/sandbox/Wildlife.flv"', $descriptorspec, $pipes);
$read = array($pipes[0]);
$write = array($pipes[1], $pipes[2]);
$except = array();
while(true)
if(($num_changed_streams = stream_select($read, $write, $except, 10)) !== false)
{
foreach($write as $stream)
var_dump(stream_get_contents($stream));
exit;
}
else
break;
每次与@Sjon 的对话 - 从 Windows 上的缓冲流读取被破坏。最后的解决方案是通过 shell 使用流重定向,然后读取创建的文件 - 如此
$descriptorspec = array
(
array("pipe", "r"),
array("pipe", "w"),
array("pipe", "w")
);
$pipes = array();
$procedure = proc_open('cd "C:/Program Files/ffmpeg/bin" && "ffmpeg.exe" -i "C:/wamp/www/sandbox/Wildlife.mp4" -deinterlace -qdiff 2 -ar 22050 "C:/wamp/www/sandbox/Wildlife.flv" > C:/stdout.log 2> C:/stderr.log', $descriptorspec, $pipes);
proc_close($procedure);
$output = file_get_contents("C:/stdout.log");
$error = file_get_contents("C:/stderr.log");
unlink("C:/stdout.log");
unlink("C:/stderr.log");
由于流是缓冲的,在文件中我们将得到未缓冲的输出(我也想要这样)。而且我们不需要检查文件是否更改,因为 shell 的结果是无缓冲和同步的。
这需要一些时间来重现,但我发现了你的问题。您 运行 的命令会在您 运行 时输出一些诊断信息;但它不输出到 stdout,而是输出到 stderr。原因在 man stderr
:
Under normal circumstances every UNIX program has three streams opened for it when it starts up, one for input, one for output, and one for printing diagnostic or error messages
如果你愿意properly use streams;这不是问题;但是您改为调用 stream_get_contents($pipes[1])
。这导致 PHP 等待来自 stdout 的输出,而该输出永远不会到达。这个修复很简单;改为从 stderr stream_get_contents($pipes[2])
读取,脚本将在进程结束后立即退出
扩展您在问题中添加的 stream_select; stream_select 未在 php 中的 windows 上实现,手册中是这样说的:
Use of stream_select() on file descriptors returned by proc_open() will fail and return FALSE under Windows.
因此,如果上面发布的代码不起作用;我不确定会发生什么。您是否考虑过放弃您的流解决方案,转而使用简单的 exec() 调用?如果您将 >%TEMP%/out.log 2>%TEMP%/err.log
附加到您的命令,您仍然可以从该过程中读取输出并且它可能会更快完成(无需等待不可修改的超时)