Socket 是兩個程序通訊連線的端點抽象。程式寫入 Socket → 網路協議棧封裝 → NIC 發送 → 對方 NIC 接收 → 協議棧解封 → 對方 Socket 讀取。核心三要素:
| 方法 | 用途 | 異常 |
|---|---|---|
getByName(String host) | 主機名/域名 → IP (單一結果) | UnknownHostException |
getAllByName(String host) | 域名 → 所有 IP 陣列 (負載平衡/輪詢) | UnknownHostException |
getLocalHost() | 本機 IP (可能回傳 127.0.0.1 需謹慎) | UnknownHostException |
getByAddress(byte[] addr) | 原始位元組陣列 → InetAddress (IPv4=4 bytes, IPv6=16 bytes) | UnknownHostException |
InetAddress ia = InetAddress.getByName("www.example.com");
System.out.println("Canonical Host: " + ia.getCanonicalHostName()); // DNS 規範名
System.out.println("IP Address: " + ia.getHostAddress()); // 數值 IP (如 93.184.216.34)
System.out.println("Host Name: " + ia.getHostName()); // 原查詢名或反解名
System.out.println("Is Loopback: " + ia.isLoopbackAddress()); // 127.0.0.1 / ::1
System.out.println("Is Multicast: " + ia.isMulticastAddress()); // 224.0.0.0/4
System.out.println("Is AnyLocal: " + ia.isAnyLocalAddress()); // 0.0.0.0 / ::
System.out.println("Address Bytes: " + Arrays.toString(ia.getAddress())); // 原始位元組
import java.net.*;
public class InetAddressDemo {
public static void main(String[] args) {
String host = (args.length > 0) ? args[0] : "localhost";
try {
InetAddress ia = InetAddress.getByName(host);
System.out.println("=== " + host + " ===");
System.out.println("Canonical: " + ia.getCanonicalHostName());
System.out.println("IP: " + ia.getHostAddress());
System.out.println("Host: " + ia.getHostName());
System.out.println("Loopback: " + ia.isLoopbackAddress());
System.out.println("Multicast: " + ia.isMulticastAddress());
System.out.println("Reachable (5s): " + ia.isReachable(5000)); // ICMP Ping
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
}
}
Client Server
| |
|--- SYN (seq=x) --------------->| ServerSocket.accept() 阻塞
| | 內核建立半連線佇列
|<-- SYN+ACK (seq=y, ack=x+1) ---| 內核回應
| |
|--- ACK (ack=y+1) ------------->| 連線建立完成
| | accept() 返回新 Socket (全連線佇列移出)
| |
|==== 數據傳輸階段 =============>|
| |
|--- FIN ----------------------->| 主動關閉
|<-- ACK ------------------------|
|<-- FIN ------------------------| 被動關閉
|--- ACK ----------------------->|
| |
// 連線建立即發生三次交握,失敗拋 IOException
Socket socket = new Socket("www.example.com", 80); // 阻塞直到連線成功/超時/拒絕
// 進階:綁定本地 IP/埠 (多網卡/防火牆需求)
Socket socket = new Socket();
socket.bind(new InetSocketAddress("192.168.1.100", 0)); // 0=系統自動分配
socket.connect(new InetSocketAddress("www.example.com", 80), 5000); // 5秒連線超時
// 取得串流
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
// 綁定監聽埠,backlog=50 (半連線佇列大小,Linux 預設 50/128)
ServerSocket server = new ServerSocket(8080, 50, InetAddress.getByName("0.0.0.0"));
// 核心迴圈
while (!shutdown) {
try {
Socket client = server.accept(); // 從全連線佇列取出已建立連線
// 處理 client (新執行緒/執行緒池/NIO)
} catch (SocketTimeoutException e) {
// accept() 超時,檢查 shutdown 旗標
}
}
| 異常 | 原因 | 排查方向 |
|---|---|---|
ConnectException: Connection refused | 對方埠未監聽/防火牆拒絕 | netstat -an | grep PORT、telnet/ nc 測試、防火牆規則 |
ConnectException: Connection timed out | 封包丟失/防火牆靜默丟棄/路由不可達 | ping、traceroute、防火牆日誌、抓包分析 |
BindException: Address already in use | 埠被佔用/TIME_WAIT 狀態 | lsof -i:PORT、SO_REUSEADDR、等待 2MSL |
UnknownHostException | DNS 解析失敗 | /etc/hosts、DNS 伺服器、拼寫錯誤 |
InetAddress.getByName 可能回傳 IPv6 位址 (Inet6Address)。ServerSocket(0) 綁定 :: 同時監聽 IPv4/IPv6 (取決於 java.net.preferIPv4Stack 系統屬性)。new Socket(host, port) 會嘗試所有解析到的位址 (Happy Eyeballs 演算法優化)。Socket 程式設計的核心難點不在 API,而在於異常處理、超時控制、資源洩漏防護、編碼一致性、半包/粘包處理。建議生產環境直接使用 Netty、gRPC、Spring WebFlux 等成熟框架,專注於業務邏輯。