Socket 是 TCP 客戶端的核心類,封裝「連接建立、雙向流通信、連接屬性查詢」。本文詳細講解:構造器選擇、輸入輸出流包裝鏈、關鍵屬性獲取方法、完整客戶端實戰代碼、常見坑點與最佳實踐。
| 構造器 | 參數 | 適用場景 |
|---|---|---|
| 已有 InetAddress 對象,避免重複解析 | 高性能、批量連接、已解析地址 | |
| 直接用主機名/IP字串,內部自動解析 | 簡單直觀,最常用 |
兩者均會:阻塞直到三次握手完成,成功返回已連接 Socket;失敗拋 IOException (連接超時/拒絕/網絡不可達)。
Socket socket = new Socket("example.com", 8080);
// 1. 原始字節流
InputStream rawIn = socket.getInputStream();
OutputStream rawOut = socket.getOutputStream();
// 2. 字符流包裝 (指定編碼!)
BufferedReader in = new BufferedReader(
new InputStreamReader(rawIn, StandardCharsets.UTF_8));
PrintWriter out = new PrintWriter(
new OutputStreamWriter(rawOut, StandardCharsets.UTF_8), true); // autoFlush
// 3. 業務收發
out.println("GET / HTTP/1.1");
out.println("Host: example.com");
out.println(); // 空行結束請求頭
String responseLine;
while ((responseLine = in.readLine()) != null) {
System.out.println(responseLine);
}
關鍵點:
StandardCharsets.UTF_8 顯式編碼,跨平台無亂碼BufferedReader.readLine() 按行讀取,適配文本協議 (HTTP, SMTP, 自定義行制協議)PrintWriter 第二參數 true = autoFlush,println() 立即發送Socket socket = new Socket("example.com", 8080);
System.out.println("本地地址: " + socket.getLocalAddress().getHostAddress()); // 客戶端 IP
System.out.println("本地端口: " + socket.getLocalPort()); // 客戶端臨時端口
System.out.println("遠程地址: " + socket.getInetAddress().getHostAddress()); // 服務端 IP
System.out.println("遠程端口: " + socket.getPort()); // 服務端端口
System.out.println("是否連接: " + socket.isConnected());
System.out.println("是否綁定: " + socket.isBound());
System.out.println("是否關閉: " + socket.isClosed());
System.out.println("是否輸入關閉: " + socket.isInputShutdown());
System.out.println("是否輸出關閉: " + socket.isOutputShutdown());
// 進階配置
socket.setSoTimeout(5000); // 讀取超時 5ms,超時拋 SocketTimeoutException
socket.setTcpNoDelay(true); // 關閉 Nagle 算法,降低延遲 (適用小包高頻)
socket.setKeepAlive(true); // 開啟 TCP KeepAlive (需 OS 支援,通常 2h 間隔)
socket.setSendBufferSize(64*1024); // 發送緩衝區
socket.setReceiveBufferSize(64*1024); // 接收緩衝區
socket.setTrafficClass(0x10); // QoS: 低延遲/高吞吐等 (IP_TOS)
public class RobustTcpClient {
private static final Logger log = LoggerFactory.getLogger(RobustTcpClient.class);
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 8080;
int maxRetries = 3;
int retryDelayMs = 1000;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try (Socket socket = new Socket()) {
// 連接帶超時
socket.connect(new InetSocketAddress(host, port), 5000);
socket.setSoTimeout(10000); // 讀取超時
log.info("連接成功: {}:{} (本地: {}:{})",
host, port, socket.getLocalAddress(), socket.getLocalPort());
try (BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
PrintWriter out = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true)) {
// 業務邏輯
out.println("HELLO");
String resp = in.readLine();
log.info("響應: {}", resp);
}
break; // 成功跳出重試循環
} catch (SocketTimeoutException e) {
log.warn("連接/讀取超時 (嘗試 {}/{}): {}", attempt, maxRetries, e.getMessage());
} catch (ConnectException e) {
log.warn("連接被拒絕 (嘗試 {}/{}): {}", attempt, maxRetries, e.getMessage());
} catch (IOException e) {
log.error("IO 異常 (嘗試 {}/{}): {}", attempt, maxRetries, e.getMessage(), e);
}
if (attempt < maxRetries) {
try { Thread.sleep(retryDelayMs); } catch (InterruptedException ignored) {}
}
}
}
}
| 坑點 | 後果 | 解法 |
|---|---|---|
| 不指定編碼用平台默認 | 跨平台/容器化部署亂碼 | 強制 StandardCharsets.UTF_8 |
| 無讀取超時 | 對端掛起導致線程永久阻塞 | socket.setSoTimeout(ms) |
| 關閉 Socket 前未 flush | 發送緩衝區數據丟失 | PrintWriter(..., true) 或手動 flush() |
| 單線程循環處理多客戶端 | 併發能力極弱 | 服務端用線程池/NIO/Netty |
| 忽略半包/粘包 | 協議解析錯誤 | 應用層定義幀邊界 (長度/分隔符/序列化) |
Socket 是 TCP 客戶端的「瑞士軍刀」。掌握:構造器選擇、流包裝鏈 (編碼/緩衝/自動刷新)、屬性查詢、超時/KeepAlive/Nagle 配置、健壯重試模式,即可編寫生產級 TCP 客戶端代碼。