PHP 的 fsockopen() 雖支援連線超時參數,但缺乏連線建立後的資料讀寫超時控制。透過 Stream 相關函式(stream_set_blocking、stream_set_timeout、stream_get_meta_data),可完整實作連線、寫入、讀取各階段的超時偵測與處理。
| 函式 | 用途 | 關鍵參數 |
|---|---|---|
stream_set_blocking($fp, false) |
設定非阻塞模式,避免 fgets()/fread() 因無資料而無限等待 |
false = 非阻塞;true = 阻塞(預設) |
stream_set_timeout($fp, 10) |
設定資料流操作超時秒數,需在連線成功後立即呼叫 | 秒數(整數),也可用陣列指定秒與微秒 |
stream_get_meta_data($fp) |
取得資料流中繼資料,包含 timed_out 布林值判斷是否超時 |
回傳陣列:['timed_out' => true/false, 'blocked' => 1, 'eof' => ...] |
<?php
$ip = '127.0.0.1';
$port = 80;
$packet = "GET / HTTP/1.1\r\nHost: $ip\r\nConnection: Close\r\n\r\n";
$fp = @fsockopen($ip, $port, $errNo, $errstr, 30); // 連線超時 30 秒
if (!$fp) {
die("Connection failed: $errstr ($errNo)");
}
// 關鍵:連線成功後立即設定讀寫超時
stream_set_timeout($fp, 3); // 3 秒讀寫超時
// stream_set_blocking($fp, false); // 視需求啟用非阻塞
// 發送資料
fwrite($fp, $packet);
$status = stream_get_meta_data($fp);
if ($status['timed_out']) {
echo "Write timed out";
fclose($fp);
exit;
}
// 讀取回應
$buf = fread($fp, 4096);
$status = stream_get_meta_data($fp);
if ($status['timed_out']) {
echo "Read timed out";
fclose($fp);
exit;
}
echo "Response received: " . strlen($buf) . " bytes";
fclose($fp);
?>
stream_set_timeout() 必須在 fsockopen() 成功後、任何讀寫操作前呼叫。fwrite()、fread() 後需立即呼叫 stream_get_meta_data() 檢查 timed_out,因超時狀態不會自動重置。stream_set_blocking($fp, false),搭配 stream_select() 使用。fsockopen 的第 5 參數能控制讀寫超時(實為連線超時)。stream_get_meta_data 回傳的 blocked、eof 等狀態資訊。實際專案建議封裝為可重用的 SocketClient 類別,整合重試機制、SSL/TLS 支援(stream_socket_client 搭配 ssl://)、協定解析(HTTP、自訂協定)等。更多 Socket 進階技巧請參考專題:《PHP Socket 用法總結》。