Contents

socket的timeout問題

在與外部廠商或第三方 API 進行 Socket 連線時,若沒有設定 timeout,程式可能會因為對方無回應而無限期等待,造成執行緒阻塞、系統資源耗盡,嚴重時會導致整個應用程式停止回應。

為什麼需要設定 Socket Timeout?

  1. 網路不穩定:連線途中網路中斷,TCP 連線可能長時間處於半開啟(half-open)狀態
  2. 伺服器無回應:對方伺服器過載或當機,不會關閉連線也不回應
  3. 防止資源耗盡:沒有 timeout 的連線會一直佔用執行緒和記憶體
  4. 用戶體驗:讓程式能在合理時間內回報錯誤,而非無限等待

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 的習慣,是寫出穩健、高可靠性系統的基本功。