在與外部廠商或第三方 API 進行 Socket 連線時,若沒有設定 timeout,程式可能會因為對方無回應而無限期等待,造成執行緒阻塞、系統資源耗盡,嚴重時會導致整個應用程式停止回應。
為什麼需要設定 Socket Timeout?
- 網路不穩定:連線途中網路中斷,TCP 連線可能長時間處於半開啟(half-open)狀態
- 伺服器無回應:對方伺服器過載或當機,不會關閉連線也不回應
- 防止資源耗盡:沒有 timeout 的連線會一直佔用執行緒和記憶體
- 用戶體驗:讓程式能在合理時間內回報錯誤,而非無限等待
PHP 設定 Socket Timeout
使用 stream_socket_client()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<?php
// 設定連線 timeout 為 5 秒
$timeout = 5;
$socket = stream_socket_client(
'tcp://example.com:80',
$errno,
$errstr,
$timeout,
STREAM_CLIENT_CONNECT
);
if (!$socket) {
echo "連線失敗: $errstr ($errno)";
} else {
// 設定讀寫 timeout
stream_set_timeout($socket, $timeout);
fwrite($socket, "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n");
$response = fread($socket, 1024);
// 檢查是否發生 timeout
$info = stream_get_meta_data($socket);
if ($info['timed_out']) {
echo "讀取逾時!";
}
fclose($socket);
}
|
使用 cURL(推薦)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<?php
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.example.com/data',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5, // 連線 timeout(秒)
CURLOPT_TIMEOUT => 30, // 整體操作 timeout(秒)
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
$error = curl_error($ch);
echo "錯誤: $error";
}
curl_close($ch);
|
Java 設定 Socket Timeout
使用 Socket 類別
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import java.net.Socket;
import java.net.InetSocketAddress;
public class SocketExample {
public static void main(String[] args) throws Exception {
Socket socket = new Socket();
// 設定連線 timeout(毫秒)
int connectTimeout = 5000; // 5 秒
// 設定讀取 timeout(毫秒)
int readTimeout = 30000; // 30 秒
socket.connect(new InetSocketAddress("example.com", 80), connectTimeout);
socket.setSoTimeout(readTimeout);
// 進行資料傳輸...
socket.close();
}
}
|
使用 HttpURLConnection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import java.net.HttpURLConnection;
import java.net.URL;
public class HttpExample {
public static void main(String[] args) throws Exception {
URL url = new URL("https://api.example.com/data");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000); // 連線 timeout 5 秒
conn.setReadTimeout(30000); // 讀取 timeout 30 秒
int responseCode = conn.getResponseCode();
System.out.println("Response: " + responseCode);
}
}
|
Timeout 設定建議
| 情境 |
連線 Timeout |
讀取 Timeout |
| 一般 REST API |
3~5 秒 |
10~30 秒 |
| 檔案下載 |
5~10 秒 |
60~300 秒 |
| 即時通訊 |
3 秒 |
5~10 秒 |
| 內部微服務 |
1~3 秒 |
5~10 秒 |
錯誤處理最佳實踐
設定 timeout 後,務必要正確處理 timeout 例外:
1
2
3
4
5
6
7
8
9
|
<?php
try {
// 連線邏輯
} catch (Exception $e) {
// 記錄錯誤日誌
error_log("Socket timeout: " . $e->getMessage());
// 回傳友善的錯誤訊息給使用者
return ['error' => '服務暫時無法使用,請稍後再試'];
}
|
養成設定 timeout 的習慣,是寫出穩健、高可靠性系統的基本功。