PHP 如果没有数据传输,流超时
PHP stream timeout if no data is transferred
我目前正在实施一个 PHP class 来获取图像文件并在本地缓存它们。这些图像可能来自其他本地源,通过 HTTP 或使用 Guzzle 客户端的 HTTP。使用 PHP 流包装器,我应该能够以相同的方式处理所有源。
我现在要做的是在没有数据通过流传输时实现超时。这应该处理以下情况:
- 一开始就无法建立流。这可能应该在
fopen
调用时处理,而不是超时。
- 流已建立,但没有数据传输。
- 流已建立,数据已传输但在传输过程中停止了一段时间。
我想我可以用 stream_set_timeout
完成所有这些,但我不太清楚它到底做了什么。如果流上的任何操作花费的时间超过允许的时间,超时是否适用,即我可以做一些需要 0.5 秒两次且超时为 0.75 秒的事情?还是仅当没有数据通过流传输的时间超过允许的时间时才适用?
我试图用这个简短的脚本测试行为:
<?php
$in = fopen('https://reqres.in/api/users?delay=5', 'r');
$out = fopen('out', 'w');
stream_set_timeout($in, 1);
stream_copy_to_stream($in, $out);
var_dump(stream_get_meta_data($in)['timed_out']);
虽然 reqres.in
的响应延迟了 5 秒,但我总是得到 false
的超时时间为 1 秒。有人可以解释一下吗?
我建议您使用 file_get_contents
和 file_put_contents
而不是流,它们支持所有包装器,您可以像向 fopen
一样向它们传递上下文。它们通常更易于使用,因为它们 return 并且接受字符串而不是流。话虽这么说,我不知道你的缓存机制的性质,如果流更适合你的用例,那么你会更有力量:)
问题
这里的问题似乎是对 fopen
如何在阻塞模式下与 http
流包装器(在我尝试之前我也没有完全理解)的工作方式产生误解。对于 GET(the default),fopen
似乎在调用时执行 HTTP 请求, 而不是 在读取流时执行。这可以解释为什么 stream_set_timeout
没有按预期运行,因为它在调用 fopen
之后修改了流上下文。
解决方案
幸运的是,有一种方法可以在调用 fopen
之前修改超时;您可以使用上下文调用 fopen
。将上下文 returned 从 stream_context_create
(如 Sammitch 链接)传递到 fopen
正确超时对于您的所有三个案例。作为参考,这是您的脚本的修改方式:
<?php
$ctx = stream_context_create(['http' => [
'timeout' => 1.0,
]]);
$in = fopen('https://reqres.in/api/users?delay=5', 'r', false, $ctx);
$out = STDOUT;
stream_copy_to_stream($in, $out);
var_dump(stream_get_meta_data($in)['timed_out']);
fclose($in);
注意:我假设您打算将流复制到标准输出而不是 "out",这在我的平台 (Darwin) 上不是有效流。我还在脚本末尾关闭了输入流,这始终是个好习惯。
这将创建一个超时为 1 的流,从调用 fopen
时开始。现在来测试你的三个条件。
验证行为
- The stream cannot be established in the first place. This should probably be handled at the fopen call and not with a timeout.
这可以正常工作——如果无法建立连接(服务器离线等),fopen
调用会立即触发警告。只需将脚本指向本地主机上没有任何监听的任意端口。 请注意,如果连接未成功建立,fopen
returns false。您必须在代码中检查这一点以避免使用 false作为流。
- The stream is established but no data is transferred.
这种情况也适用,只是 运行 脚本与您的正常 URL。这也会使 fopen
return 为假并触发警告(不同的警告)。
- The stream is established, data is transferred but it stops some time during transfer.
这是一个有趣的案例。要对此进行测试,您可以编写一个脚本来发送 Content-Length
和其他一些 headers 以及一些部分数据,然后等到超时,即:
<?php
header('Content-Type: text/plain');
header('Content-Length: 10');
echo "hi";
ob_flush();
sleep(10);
ob_flush
是使 PHP 在睡眠和脚本退出之前写入输出(不关闭连接)所必需的。您可以使用 php -S localhost:port
提供此服务,然后将另一个脚本指向 localhost:port
。在这种情况下,客户端脚本不会抛出警告,并且 fopen
实际上 return 是元数据中 timed_out
设置为 true 的流。
结论
stream_set_timeout
不适用于 HTTP GET 请求和 fopen
在阻塞模式下 因为 fopen
在调用时执行请求而不是等待阅读这样做。您可以通过超时将上下文传递给 fopen
来解决此问题。
"read time out"和"connection time out"是有区别的..
连接超时是建立初始连接(完成TCP连接握手)的超时。
读取超时是等待读取数据的超时时间。如果服务器在最后一个字节后 XX 秒没有发送一个字节,则会产生读取超时错误。
即使您看到 5 秒的延迟(响应时间)- 这可能发生在初始连接(DNS 查找、连接等)期间,而不是在您读取期间。
我目前正在实施一个 PHP class 来获取图像文件并在本地缓存它们。这些图像可能来自其他本地源,通过 HTTP 或使用 Guzzle 客户端的 HTTP。使用 PHP 流包装器,我应该能够以相同的方式处理所有源。
我现在要做的是在没有数据通过流传输时实现超时。这应该处理以下情况:
- 一开始就无法建立流。这可能应该在
fopen
调用时处理,而不是超时。 - 流已建立,但没有数据传输。
- 流已建立,数据已传输但在传输过程中停止了一段时间。
我想我可以用 stream_set_timeout
完成所有这些,但我不太清楚它到底做了什么。如果流上的任何操作花费的时间超过允许的时间,超时是否适用,即我可以做一些需要 0.5 秒两次且超时为 0.75 秒的事情?还是仅当没有数据通过流传输的时间超过允许的时间时才适用?
我试图用这个简短的脚本测试行为:
<?php
$in = fopen('https://reqres.in/api/users?delay=5', 'r');
$out = fopen('out', 'w');
stream_set_timeout($in, 1);
stream_copy_to_stream($in, $out);
var_dump(stream_get_meta_data($in)['timed_out']);
虽然 reqres.in
的响应延迟了 5 秒,但我总是得到 false
的超时时间为 1 秒。有人可以解释一下吗?
我建议您使用 file_get_contents
和 file_put_contents
而不是流,它们支持所有包装器,您可以像向 fopen
一样向它们传递上下文。它们通常更易于使用,因为它们 return 并且接受字符串而不是流。话虽这么说,我不知道你的缓存机制的性质,如果流更适合你的用例,那么你会更有力量:)
问题
这里的问题似乎是对 fopen
如何在阻塞模式下与 http
流包装器(在我尝试之前我也没有完全理解)的工作方式产生误解。对于 GET(the default),fopen
似乎在调用时执行 HTTP 请求, 而不是 在读取流时执行。这可以解释为什么 stream_set_timeout
没有按预期运行,因为它在调用 fopen
之后修改了流上下文。
解决方案
幸运的是,有一种方法可以在调用 fopen
之前修改超时;您可以使用上下文调用 fopen
。将上下文 returned 从 stream_context_create
(如 Sammitch 链接)传递到 fopen
正确超时对于您的所有三个案例。作为参考,这是您的脚本的修改方式:
<?php
$ctx = stream_context_create(['http' => [
'timeout' => 1.0,
]]);
$in = fopen('https://reqres.in/api/users?delay=5', 'r', false, $ctx);
$out = STDOUT;
stream_copy_to_stream($in, $out);
var_dump(stream_get_meta_data($in)['timed_out']);
fclose($in);
注意:我假设您打算将流复制到标准输出而不是 "out",这在我的平台 (Darwin) 上不是有效流。我还在脚本末尾关闭了输入流,这始终是个好习惯。
这将创建一个超时为 1 的流,从调用 fopen
时开始。现在来测试你的三个条件。
验证行为
- The stream cannot be established in the first place. This should probably be handled at the fopen call and not with a timeout.
这可以正常工作——如果无法建立连接(服务器离线等),fopen
调用会立即触发警告。只需将脚本指向本地主机上没有任何监听的任意端口。 请注意,如果连接未成功建立,fopen
returns false。您必须在代码中检查这一点以避免使用 false作为流。
- The stream is established but no data is transferred.
这种情况也适用,只是 运行 脚本与您的正常 URL。这也会使 fopen
return 为假并触发警告(不同的警告)。
- The stream is established, data is transferred but it stops some time during transfer.
这是一个有趣的案例。要对此进行测试,您可以编写一个脚本来发送 Content-Length
和其他一些 headers 以及一些部分数据,然后等到超时,即:
<?php
header('Content-Type: text/plain');
header('Content-Length: 10');
echo "hi";
ob_flush();
sleep(10);
ob_flush
是使 PHP 在睡眠和脚本退出之前写入输出(不关闭连接)所必需的。您可以使用 php -S localhost:port
提供此服务,然后将另一个脚本指向 localhost:port
。在这种情况下,客户端脚本不会抛出警告,并且 fopen
实际上 return 是元数据中 timed_out
设置为 true 的流。
结论
stream_set_timeout
不适用于 HTTP GET 请求和 fopen
在阻塞模式下 因为 fopen
在调用时执行请求而不是等待阅读这样做。您可以通过超时将上下文传递给 fopen
来解决此问题。
"read time out"和"connection time out"是有区别的..
连接超时是建立初始连接(完成TCP连接握手)的超时。 读取超时是等待读取数据的超时时间。如果服务器在最后一个字节后 XX 秒没有发送一个字节,则会产生读取超时错误。
即使您看到 5 秒的延迟(响应时间)- 这可能发生在初始连接(DNS 查找、连接等)期间,而不是在您读取期间。