後端服務間呼叫若無超時保護,一個掛住的下游即可拖垮整個上游叢集。本文系統整理 cURL、Socket、Stream 三大呼叫方式的超時控制細節,並提供可直接落地的程式碼範本。
<?php
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_USERAGENT => $_SERVER['HTTP_USER_AGENT'] ?? 'PHP/cURL',
]);
$data = curl_exec($ch);
?>
<?php
$ch = curl_init('https://example.com/');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_NOSIGNAL => 1, // 必須!避免訊號中斷
CURLOPT_TIMEOUT_MS => 200, // 200 ms 總超時
]);
$data = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
curl_close($ch);
if ($errno) {
error_log("cURL Error ($errno): $error");
} else {
echo "Data received: $data";
}
?>
curl_multi 注意事項curl_multi 並行請求時,毫秒級超時可能不準確;單請求較穩定。<?php
$tmStart = gettimeofday();
// ... 執行 socket 讀寫 ...
$tmCurrent = gettimeofday();
$elapsedUs = ($tmCurrent['sec'] - $tmStart['sec']) * 1_000_000
+ ($tmCurrent['usec'] - $tmStart['usec']);
if ($elapsedUs > $readTimeoutUs) {
return false; // 超時中斷
}
?>
<?php
$timeout = 5; // 秒
$fp = fsockopen('example.com', 80, $errno, $errstr, $timeout);
if ($fp) {
fwrite($fp, "GET / HTTP/1.0\r\nHost: example.com\r\nConnection: Close\r\n\r\n");
stream_set_blocking($fp, true); // 同步阻塞模式
stream_set_timeout($fp, $timeout); // 設定讀寫超時
$data = '';
$info = stream_get_meta_data($fp);
while (!feof($fp) && !$info['timed_out']) {
$data .= fgets($fp, 4096);
$info = stream_get_meta_data($fp); // 每輪更新 timed_out 旗標
}
if ($info['timed_out']) {
echo "Connection Timed Out!";
} else {
echo $data;
}
fclose($fp);
}
?>
<?php
$ctx = stream_context_create([
'http' => ['timeout' => 5] // 秒
]);
$text = file_get_contents('https://example.com/', 0, $ctx);
// 或
$fp = fopen('https://example.com/', 'r', false, $ctx);
?>
| 場景 | 推薦方案 | 關鍵提醒 |
|---|---|---|
| 一般 HTTP API 呼叫 | cURL + CURLOPT_TIMEOUT | 務必設定連線與總超時分離 |
| 需毫秒級精確控制 | cURL + CURLOPT_TIMEOUT_MS + CURLOPT_NOSIGNAL | 確認 cURL ≥ 7.21.0 |
| 無法安裝 cURL 擴充 | Stream Context + stream_set_timeout | 需手動檢查 timed_out 旗標 |
| 長連線 / WebSocket / 自定協定 | Socket + 手動計時或 non-blocking + select() | 自行管理心跳與重連 |
超時不是可有可無的選項,而是分散式系統生存的底線。請根據業務 SLA 選擇適當層級與精度,並在程式碼審查中強制檢查「每個外部呼叫都有超時設定」。