stream_select

(PHP 4 >= 4.3.0, PHP 5, PHP 7, PHP 8)

stream_select在给定的流数组上运行等效于 select() 系统调用的操作,超时时间由秒和微秒指定

说明

stream_select(
    ?array &$read,
    ?array &$write,
    ?array &$except,
    ?int $seconds,
    ?int $microseconds = null
): int|false

stream_select() 函数接收流数组,并等待它们状态改变。它的操作等效于 socket_select() 函数,但它作用于流。

参数

read

read 数组中列出的流将被监视,以查看是否有字符可供读取(更准确地说,是查看读取是否不会阻塞 - 特别是,流资源在文件结束时也是就绪的,在这种情况下,fread() 将返回一个零长度字符串)。

write

write 数组中列出的流将被监视,以查看写入是否不会阻塞。

except

except 数组中列出的流将被监视,以查看是否有高优先级异常(“带外”)数据到达。

注意:

stream_select() 返回时,数组 readwriteexcept 将被修改以指示哪些流资源实际改变了状态。array 的原始键将被保留。

seconds

secondsmicroseconds 一起构成超时参数,seconds 指定秒数,而 microseconds 指定微秒数。timeoutstream_select() 在返回之前等待的时间的上限。如果 secondsmicroseconds 都设置为 0,则 stream_select() 不会等待数据 - 相反,它将立即返回,指示流的当前状态。

如果 secondsnullstream_select() 可以无限期阻塞,只有在监视的流之一发生事件时(或信号中断系统调用时)才会返回。

警告

使用 0 的超时值允许你立即轮询流的状态,但是,在循环中使用 0 的超时值不是一个好主意,因为它会导致你的脚本消耗过多的 CPU 时间。

最好指定几秒钟的超时值,尽管如果你需要同时检查和运行其他代码,使用至少 200000 微秒的超时值将有助于减少脚本的 CPU 使用率。

请记住,超时值是将要过去的最长时间;stream_select() 只要请求的流准备就绪即可返回。

microseconds

参见 seconds 说明。

返回值

成功时,stream_select() 返回修改后的数组中包含的流资源数量,如果超时时间在任何有趣的事情发生之前到期,则该数量可能为零。发生错误时,将返回 false 并发出警告(如果系统调用被传入的信号中断,则可能会发生这种情况)。

变更日志

版本 说明
8.1.0 microseconds 现在可以为 null。

示例

示例 #1 stream_select() 示例

此示例检查 $stream1$stream2 上是否有数据可供读取。由于超时值为 0,因此它将立即返回

<?php
/* 准备读取数组 */
$read = array($stream1, $stream2);
$write = NULL;
$except = NULL;
if (
false === ($num_changed_streams = stream_select($read, $write, $except, 0))) {
/* 错误处理 */
} elseif ($num_changed_streams > 0) {
/* 至少在一个流上发生了有趣的事情 */
}
?>

备注

注意:

由于当前 Zend 引擎的限制,无法将常量修饰符(如 null)直接作为参数传递给期望此参数通过引用传递的函数。而是使用一个临时变量或一个以临时变量为最左侧成员的表达式

<?php
$e
= NULL;
stream_select($r, $w, $e, 0);
?>

注意:

在检查错误时,务必使用 === 运算符。由于 stream_select() 可能返回 0,因此与 == 的比较将评估为 true

<?php
$e
= NULL;
if (
false === stream_select($r, $w, $e, 0)) {
echo
"stream_select() failed\n";
}
?>

注意:

如果你在数组中返回的流上进行读写,请注意,它们不一定能读写你请求的全部数据。甚至可能只读写一个字节。

注意:

有些流(比如 zlib)无法通过此函数选择。

注意: Windows 兼容性

在 Windows 下,对 proc_open() 返回的文件描述符使用 stream_select() 将失败并返回 false

来自控制台的 STDIN 只要有任何输入事件可用就会改变状态,但从流中读取可能仍然会阻塞。

参见

添加注释

用户贡献的注释 17 个注释

php at richardneill dot org
15 年前
请注意,从已处于文件结尾的常规文件中读取 *不会* 阻塞。您将获得非阻塞的零字节读取。但是,如果输入是管道并且没有更多数据可以读取,则 *stream_select* *将* 阻塞。
Martin
6 年前
维护与多个客户端的连接可能很棘手,PHP 脚本是单线程进程,因此如果您想同时执行多项操作(例如等待新连接和等待新数据),则必须使用某种多路复用。

<?php
$socket
= stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
stream_set_blocking($socket, 0);

$connections = [];
$read = [];
$write = null;
$except = null;

while (
1) {

// 查找新连接
if ($c = @stream_socket_accept($socket, empty($connections) ? -1 : 0, $peer)) {
echo
$peer.' connected'.PHP_EOL;
fwrite($c, 'Hello '.$peer.PHP_EOL);
$connections[$peer] = $c;
}

// 等待任何流数据
$read = $connections;
if (
stream_select($read, $write, $except, 5)) {

foreach (
$read as $c) {
$peer = stream_socket_get_name($c, true);

if (
feof($c)) {
echo
'Connection closed '.$peer.PHP_EOL;
fclose($c);
unset(
$connections[$peer]);
} else {
$contents = fread($c, 1024);
echo
$peer.': '.trim($contents).PHP_EOL;
}
}
}
}
?>
aidan at php dot net
20 年前
如果您想为循环中的 stream_select 设置绝对最大执行时间,则必须递减传递给 stream_select 的 max_time 值。

<?php
// 以毫秒为单位的最大执行时间
$maxtime = 200000;
// 循环开始的时间
$starttime = microtime(true);
// 原始套接字数组
$r = $orig_sockets;

// 计算超时时间的函数
function calctimeout($maxtime, $starttime)
{
return
$maxtime - ((microtime(true) - $starttime) * 1000000);
}

while (
stream_select($r, $w = null, $e = null, 0, calctimeout($maxtime, $starttime)) !== 0)
{
// 循环遍历显示活动的套接字
foreach ($r as $socket) {
// $socket 发生通信
}

// stream_select 会修改 $r 的内容
// 在循环中,我们应该用原始值替换它
$r = $orig_sockets;
}

?>
Sitsofe
5 年前
当 stream_select() 失败时,您 *不应该* 使用传递给它的参数作为参数的数组(即读取、写入、异常)的结果。虽然这样做不会触发未定义的行为,但您依赖于未指定的行为,而这种行为默认情况下是不能保证的。

在撰写本文时,PHP 7.2 解释器在 stream_select() 失败时不会修改数组(参见 https://github.com/php/php-src/blob/php-7.2.14/ext/standard/streamsfuncs.c#L842 附近的代码),因此不遵守上述建议的 PHP 程序可能会欺骗自己关于这些流的状态。

(希望有一天可以在主文档中添加此警告)
asphp at dsgml dot com
8 年前
确保不要在 stream_select 的 3 个参数中传递相同的变量,否则您只会获得其中一个的结果,而其他结果将被覆盖。
maartenwolzak at gmail dot com
17 年前
请注意,您应该更改下面的 calctimeout 函数,将结果除以 1.000.000,否则您将等待两年而不是一分钟来让套接字超时...

<?php

// 计算超时时间的函数
function calctimeout($maxtime, $starttime)
{
return (
$maxtime - ((microtime(true) - $starttime) * 1000000))/1000000;
}

?>
mark2xv at gmail dot com
10 年前
如果您在使用 stream_select 的非阻塞套接字时遇到无法解释的问题,请使用以下方法禁用缓冲区

stream_set_read_buffer($socket, 0);
stream_set_write_buffer($socket, 0);

出于某种原因,在写入(总计)~256k 时,套接字在读取时开始返回 FALSE,但始终出现在 stream_select 数组中。这解决了这个问题。(对我们来说)。
phpdoc at shemesh dot biz
19 年前
请注意,在返回时,"read" 的键将是基于零的,并根据只有准备好读取数据的流进行连续编号。换句话说,如果您想知道最初放置在 "read" 中的哪个流已准备好,则没有直接的方法可以知道。

如果您想知道哪个原始流是哪个流,您可以使用 "==",或者可能设置一个反向映射数组,其中流是键,而原始 "read" 数组的键是数据。
rob at associatedtechs dot com
10 年前
如果您尝试将 stream_select() 与 fread() 一起使用,您可能会遇到一系列错误的组合(https://bugs.php.net/bug.php?id=52602https://bugs.php.net/bug.php?id=51056)。从 PHP 5.5.10 开始,fread() 和 stream_select() 不能可靠地协同工作。

如果您需要 stream_select() 并且不需要加密连接(例如 TLS),请使用 stream_socket_recvfrom() 而不是 fread()。

我找不到在 PHP 中使用阻塞函数可靠地处理加密连接的方法;非阻塞可能是唯一的方法。
Ben
17 年前
您可以通过将文件描述符强制转换为 int 或字符串来键入文件描述符,这将返回您所期望的结果。
doingitwrong at mailismagic dot com
9 年前
如果您将 stream_select() 与阻塞流一起使用,那您就错了!

仅仅因为此函数在其中一个或多个数组中返回某些内容 *并不* 意味着将来的读取或写入操作不会阻塞。

上面的句子是您将在流操作方面阅读的最重要的一句话。将 `stream_select()` 与阻塞流一起使用是一个非常常见的初学者错误,并且在追踪此函数和类似的 `select()` 系统函数的使用时会导致重大问题。PHP(以及底层操作系统)应该验证提供的流集是否是非阻塞的,如果任何套接字被设置为阻塞,则应该抛出错误/异常,以迫使人们修复他们的代码。`stream_select()` 的文档充其量是误导性的。

如果您想要一个非阻塞流,则将流设置为非阻塞。否则,接受阻塞流。毕竟,阻塞的全部意义在于无限期地阻塞,直到操作完成。`select()` 仅适用于非阻塞流。任何其他使用都会导致非常难以追踪的错误。

多年前,我在遇到我提到的错误后,得到了上述指导。我修复了我的代码,现在在其他地方遇到此问题时,我会纠正类似的错误。为非阻塞流编写代码比尝试使用 `select()` 函数为阻塞流编写补丁并最终导致应用程序错误更简单。
aks at esoft dot dk
13 年前
`stream_select()` 看起来像是 POSIX `select(2)` 的一个简单包装器。

但请注意:虽然 `select(2)` 允许您传递没有文件描述符并将其用作“可移植亚秒睡眠”,但如果所有数组为空或为 null,PHP 会抱怨“警告:stream_select(): 未传递任何流数组 ****”,并且它不会睡眠,而是会立即返回。所以……如果您拥有的文件描述符数量不是静态的,您必须自己处理特殊情况。
jerrywilborn at gmail dot com
15 年前
http://bugs.php.net/bug.php?id=42682

此函数在某些带有旧版本的系统下不会返回更改的流数量,而是返回 '0'。小心。
bluej100@gmail
16 年前
@mbaynton at gmail dot com

一个方便的语法技巧

<?php
$r
= Array($stream1, $stream2);
stream_select($r, $w = null, $x = null, 1337);
?>

我在文档的其他地方看到有人推荐它,用于阐明魔法参数,以便维护人员不必去检查函数本身,但这也能解决这里的问题。
aidan at aidans dot org
17 年前
请注意,与之前的发布者所说相反,无法将流资源用作数组的键。相反,如果您想知道正在处理哪个套接字,请考虑使用类似于以下代码的代码

<?php
$sockets
= array("sock_1" => $sock1, "sock_2" => $sock2, "sock_3" => $sock_3);

$read = $write = $error = $sockets;
$num = stream_select($read, $write, $error, 10);
if (
$n > 0) {
foreach (
$read as $r) {
$key = array_search($r, $sockets);
// $key 将是 "sock_1"、"sock_2"、"sock_3" 等。
}
}
?>

希望这对某些人有所帮助!
Maxdamantus
16 年前
简单的 `stream_select` 包装器.. 返回数组中的第一个流,并将参数 2 设置为键(以便易于识别接收到的数据)

<?php
function select($array, &$vkey, $timeout=0){
$select = array();
$null = NULL;
foreach(
$array as $key => $sock){
$x = count($select);
$select[$x] = $sock;
$keys[$x] = $key;
}
if(
stream_select($select, $null, $null, $timeout)){
foreach(
$keys as $key){
if(
$array[$key] == $select[0]){
$vkey = $key;
return(
$select[0]);
}
}
}
}

$streams = array("foo" => $stream_one, "bar" => $stream_two); // 创建两个(已存在的)流的数组。
if($new = select($streams, $key, 60)){ // 将 $new 设置为下一个获取新数据的资源,将 $key 设置为 "foo" 或 "bar",具体取决于哪个。
echo $key.":".stream_get_line($new, 2048)."\n";
}
?>
lynch at php dot net
14 年前
注意:至少一个输入数组必须非空,否则您会收到 E_WARNING 消息
PHP 警告:stream_select(): 无法选择 [9]: 坏文件描述符 (max_fd=0)
To Top