Socket 是 Java 客戶端網路通訊的核心類。本文詳細講解建立連接、發送接收數據、關閉連接的標準三步流程,並深入剖析關閉機制的 4 種方式與流/連接狀態檢測方法。
new Socket(host, port) 或 connect()getOutputStream() / getInputStream()socket.close()支援 IP 或域名直連,域名會自動經 DNS 解析:
// 命令行參數傳入目標主機
Socket socket = new Socket(args[0], 80); // 連接 80 端口
System.out.println(args[0] + " 已連接成功!");
異常處理:捕獲 UnknownHostException (域名無效) 與 IOException (連接失敗/超時)。
public class PortScanner extends Thread {
private int minPort, maxPort;
public PortScanner(int min, int max) { this.minPort = min; this.maxPort = max; }
public void run() {
for (int p = minPort; p <= maxPort; p++) {
try (Socket s = new Socket("127.0.0.1", p)) {
System.out.println(p + ": open");
} catch (Exception ignored) {}
}
}
public static void main(String[] args) {
int min = Integer.parseInt(args[0]), max = Integer.parseInt(args[1]), threads = Integer.parseInt(args[2]);
int range = (max - min + 1 + threads - 1) / threads;
for (int i = 0; i < threads; i++) {
new PortScanner(min + i * range, Math.min(max, min + (i+1)*range - 1)).start();
}
}
}
運行:java PortScanner 1 1000 20 — 20 線程掃描 1~1000 端口。
核心 API:getOutputStream() 發送,getInputStream() 接收。建議包裝為緩衝字符流:
Socket socket = new Socket("www.example.com", 80);
// 發送 HTTP 請求頭 (以 \r\n\r\n 結束)
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.ISO_8859_1));
bw.write("GET / HTTP/1.1\r\nHost: www.example.com\r\nConnection: close\r\n\r\n");
bw.flush(); // 必須 flush 將緩衝區數據推入網路
// 接收響應
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
socket.close();
關鍵細節:
flush()觸發實際發送,預設 8KB 緩衝區。- HTTP 協議頭以
\r\n\r\n結束,缺少會導致服務端持續等待,客戶端readLine()阻塞至超時。- 字符編碼需雙端一致,HTTP 頭通常用 ISO-8859-1,Body 視 Content-Type 而定。
socket.close() — 顯式關閉,**推薦**InputStream 或 OutputStream — 流關閉導致 Socket 關閉finalize() 關閉(不可靠,**不推薦**)前兩種屬於主動可控關閉,後兩種屬於被動不可控,生產環境務必使用方式 1 或 2。
| 方法 | 含義 | 關閉後 | 未連接 | 連接中 |
|---|---|---|---|---|
isClosed() | 對象是否處於關閉狀態 | true | false (未調用 close) / true (調用 close) | false |
isConnected() | 是否曾經連接成功 | true | false | true |
判斷當前是否連接中:!socket.isClosed() && socket.isConnected()
Socket s1 = new Socket("www.example.com", 80);
printState(s1, "s1"); // closed=false, connected=true -> 連接中
s1.getOutputStream().close(); // 關閉流導致 Socket 關閉
printState(s1, "s1"); // closed=true, connected=true -> 非連接中
Socket s2 = new Socket();
printState(s2, "s2"); // closed=false, connected=false -> 非連接中
s2.close(); // 未連接的 Socket 必須顯式 close 才能使 isClosed=true
printState(s2, "s2"); // closed=true, connected=false
s1.isClosed():false
s1.isConnected():true
s1 處於連接狀態!
s1.isClosed():true
s1.isConnected():true
s1 處於非連接狀態!
s2.isClosed():false
s2.isConnected():false
s2 處於非連接狀態!
s2.isClosed():true
s2.isConnected():false
s2 處於非連接狀態!
僅關閉單向流,**不關閉 Socket**,適合雙工協議結束單向傳輸:
Socket s = new Socket("host", 80);
s.shutdownInput(); // 不再接收,發送仍可用
s.shutdownOutput(); // 不再發送,接收仍可用
// 狀態檢測
s.isInputShutdown(); // true
s.isOutputShutdown(); // true
s.isClosed(); // false (Socket 仍開啟)
try-with-resources (Java 7+) 自動關閉 Socket 與流。flush()。\r\n\r\n、Content-Length、分隔符),避免 read() 無限阻塞。