網路通訊中,超時是保護客戶端不至無限等待的關鍵機制。Java Socket 提供兩種獨立的超時控制:連接超時與讀取超時。本文深入剖析其原理、設置方式、異常處理及實測驗證。
| 超時類型 | 觸發階段 | 設置方式 | 拋出異常 |
|---|---|---|---|
| 連接超時 | TCP 三次握手階段 | socket.connect(address, timeoutMs) | SocketTimeoutException |
| 讀取超時 | 數據接收階段 (read() 阻塞) | socket.setSoTimeout(timeoutMs) | SocketTimeoutException (繼承 InterruptedIOException) |
僅能通過 connect(SocketAddress, int timeout) 設置,單位毫秒。0 表示使用系統預設(通常較長)。無效 IP/端口、防火牆阻擋、服務端未監聽均會觸發:
Socket socket = new Socket();
socket.connect(new InetSocketAddress("192.168.1.100", 8080), 3000); // 3 秒連接超時
必須在 讀取操作前 調用。0 表示無限等待(預設),此時完全依賴服務端斷開連接。超時拋出 SocketTimeoutException,但 輸入流未關閉,可繼續嘗試讀取:
Socket socket = new Socket();
socket.setSoTimeout(5000); // 5 秒讀取超時
socket.connect(...);
socket.getInputStream().read(); // 超過 5 秒無數據到達則拋出異常
public class SocketTimeoutTest {
public static void main(String[] args) {
if (args.length < 4) { System.out.println("Usage: host port connectTimeout readTimeout"); return; }
String host = args[0];
int port = Integer.parseInt(args[1]);
int connTimeout = Integer.parseInt(args[2]);
int readTimeout = Integer.parseInt(args[3]);
Socket socket = new Socket();
long start = 0, end = 0;
try {
start = System.currentTimeMillis();
socket.connect(new InetSocketAddress(host, port), connTimeout);
socket.setSoTimeout(readTimeout);
start = System.currentTimeMillis(); // 重置計時器
socket.getInputStream().read(); // 觸發讀取超時
} catch (SocketTimeoutException e) {
if (!socket.isClosed() && socket.isConnected())
System.out.println("讀取數據超時!");
else
System.out.println("連接超時");
} catch (Exception e) {
System.out.println("其他錯誤: " + e.getMessage());
} finally {
end = System.currentTimeMillis();
System.out.println("耗時: " + (end - start) + "ms");
}
}
}
連接超時 ~3045ms連接超時 ~3075ms讀取數據超時! ~5008msConnection reset ~131519ms (2分+),由服務端主動斷開決定關鍵觀察:
- 實際耗時會在設定值附近波動(系統調度、異常處理、輸出開銷)。
- 超時值不可設太小(如 100ms),否則正常網路延遲也會誤判超時。
- 讀取超時 0 時,客戶端完全被動等待服務端斷開,失去自主控制權。
try {
socket.connect(address, 3000);
socket.setSoTimeout(5000);
int data = socket.getInputStream().read();
} catch (SocketTimeoutException e) {
if (socket.isConnected() && !socket.isClosed()) {
// 讀取超時:可選擇重試、記錄日誌或優雅關閉
log.warn("Read timeout, retry or close");
} else {
// 連接超時
log.error("Connection timeout");
}
} catch (IOException e) {
log.error("Network error", e);
} finally {
closeQuietly(socket);
}
connect() 參數控制,屬於主動發起階段。setSoTimeout() 控制,屬於被動接收階段。