2024 日本 PHP 研討會

stream_select

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

stream_select對指定的串流陣列執行等同於 select() 系統呼叫的操作,並以秒和微秒指定逾時

說明

stream_select(
    ?陣列 &$read,
    (?陣列) &$write,
    (?陣列) &$except,
    (?整數) $seconds,
    (?整數) $microseconds = null
): 整數|false

stream_select() 函式接受數據流陣列,並等待它們的狀態改變。它的操作等同於 socket_select() 函式,不同之處在於它作用於數據流。

參數

read

列在 read 陣列中的數據流將被監視,以查看是否有字符可供讀取(更準確地說,是查看讀取是否不會阻塞——特別是,數據流資源在文件末尾也準備就緒,在這種情況下,fread() 將返回一個零長度的字符串)。

write

列在 write 陣列中的數據流將被監視,以查看寫入是否不會阻塞。

except

列在 except 陣列中的數據流將被監視,以查看是否有高優先級的異常(「帶外」)數據到達。

注意事項:

stream_select() 返回時,readwriteexcept 陣列將被修改,以指示哪些數據流資源實際更改了狀態。陣列的原始鍵值將被保留。

seconds

secondsmicroseconds 一起組成 *timeout* 參數,seconds 指定秒數,而 microseconds 指定微秒數。*timeout* 是 stream_select() 在返回之前等待時間的上限。如果 secondsmicroseconds 都設為 0stream_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任何輸入事件可用時就會更改狀態,但從串流讀取可能仍然會阻塞。

另請參閱

新增註記

使用者貢獻的註記 12 則註記

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.' 已連線'.PHP_EOL;
fwrite($c, '您好 '.$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
'連線已關閉 '.$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);
// 原始 sockets 陣列
$r = $orig_sockets;

// 計算 timeout 的函式
function calctimeout($maxtime, $starttime)
{
return
$maxtime - ((microtime(true) - $starttime) * 1000000);
}

while (
stream_select($r, $w = null, $e = null, 0, calctimeout($maxtime, $starttime)) !== 0)
{
// 迴圈處理有活動的 sockets
foreach ($r as $socket) {
// $socket 有動作
}

// stream_select 會修改 $r 的內容
// 在迴圈中,我們應該將它替換回原始值
$r = $orig_sockets;
}

?>
maartenwolzak at gmail dot com
17 年前
請注意,您應該將下面的 calctimeout 函式修改為將結果除以 1,000,000,否則您將等待兩年而不是一分鐘讓 socket 超時...

<?php

// 計算 timeout 的函式
function calctimeout($maxtime, $starttime)
{
return (
$maxtime - ((microtime(true) - $starttime) * 1000000))/1000000;
}

?>
Sitsofe
5 年前
當 stream_select() 失敗時,您不應該使用作為參數傳入的陣列(例如 read、write、except)的結果。雖然這樣做不會觸發未定義行為,但您依賴的是未指定的行為,根據定義,這是不保證的。

在撰寫本文時,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 個參數中傳遞相同的變數,否則您只會得到其中一個的結果,而其他參數將被覆蓋。
mark2xv at gmail dot com
11 年前
如果您在使用 `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 年前
您可以透過將檔案描述符轉換為整數或字串來將其作為鍵值,這將會返回您預期的結果。
doingitwrong at mailismagic dot com
9 年前
如果您將 `stream_select()` 與阻塞式串流一起使用,那就是錯誤的做法!

這個函式在一個或多個陣列中返回某些內容,並不表示未來的讀取或寫入操作不會阻塞。

以上這句話是您在串流操作方面會讀到的最重要的一句話。將 `stream_select()` 與阻塞式串流一起使用是一個非常常見的初學者錯誤,並且在追蹤此函式和類似 `select()` 系統函式的使用情況時會造成很大的困擾。PHP(實際上是底層作業系統)應該驗證提供的串流集是否是非阻塞式的,如果任何通訊端設定為阻塞,則拋出錯誤/例外,以便強制人們修復他們的程式碼。`stream_select()` 的文件充其量只是誤導。

如果您想要非阻塞式串流,則將串流設定為非阻塞式。否則,請使用阻塞式串流。畢竟,這就是阻塞的重點——無限期地阻塞,直到操作完成。`select()` 僅適用於非阻塞式串流。任何其他用途都會導致非常難以追蹤的錯誤。

我在多年前遇到我提到的錯誤之後,得到了上述的教訓。我修復了我的程式碼,現在當我在其他地方遇到這個問題時,也會更正類似的錯誤。編寫非阻塞式串流的程式碼比嘗試使用 `select()` 函式編寫阻塞式串流的 hacks 並最終導致應用程式錯誤要簡單得多。
aks at esoft dot dk
14 年前
`stream_select()` 看似只是 POSIX `select(2)` 的一個簡單包裝函式。

但要注意:雖然 `select(2)` 允許您不傳遞任何檔案描述符並將其用作「可攜式的次秒級睡眠」,但如果所有陣列都為空或 null,PHP 會發出「警告:stream_select(): 沒有傳遞任何串流陣列 ****」的警告,而且它不會睡眠,會立即返回。因此... 如果您擁有的檔案描述符數量不是靜態的,您必須自行處理這種特殊情況。
To Top