Socket(套接字)是計算機網絡通信的基礎設施。瀏覽器、即時通訊、P2P 下載等幾乎所有網絡應用都建立在 Socket 之上。本文聚焦 基於 TCP/IP 的流式 Socket 編程,涵蓋客戶端/服務端完整開發流程。
| 維度 | TCP(流式) | UDP(數據報) |
|---|---|---|
| 連接性 | 面向連接(三次握手) | 無連接 |
| 可靠性 | 可靠、有序、無重複 | 不可靠、無序、可能丟包/重複 |
| 數據邊界 | 無邊界(流式),需應用層分幀 | 保留消息邊界 |
| 大小限制 | 無實際限制 | 單數據報 ≤ 64KB |
| 開銷 | 較大(連接建立/維護) | 極小 |
| 適用場景 | HTTP、FTP、SSH、郵件、數據庫 | DNS、視頻流、即時遊戲、廣播 |
選型原則:數據完整性關鍵 → TCP;實時性優先、可容忍丟包 → UDP。
全部位於 java.net 包:
Socket:客戶端套接字ServerSocket:服務端監聽套接字InetAddress:IP 地址封裝InetSocketAddress:IP + 端口組合端口選擇:0~1023 為系統保留(HTTP 80、HTTPS 443、SSH 22 等),自定義服務請使用 1024 以上。
public class TcpClient {
public static void main(String[] args) {
String host = "127.0.0.1";
int port = 8919;
try (Socket socket = new Socket(host, port);
Writer writer = new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8)) {
// 發送數據
writer.write("Hello from Client!");
writer.flush(); // 關鍵:強制發送緩衝區數據
// 如需接收響應
try (Reader reader = new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)) {
char[] buffer = new char[1024];
int len = reader.read(buffer);
if (len > 0) System.out.println("Server reply: " + new String(buffer, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
關鍵點:
try-with-resources 自動關閉資源(JDK 7+)flush() 確保數據發送,否則可能滯留緩衝區
public class TcpServer {
public static void main(String[] args) {
int port = 8919;
try (ServerSocket server = new ServerSocket(port)) {
System.out.println("Server listening on port " + port);
while (true) { // 持續接受客戶端
try (Socket client = server.accept(); // 阻塞等待連接
Reader reader = new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8);
Writer writer = new OutputStreamWriter(client.getOutputStream(), StandardCharsets.UTF_8)) {
// 讀取完整消息
StringBuilder msg = new StringBuilder();
char[] buffer = new char[1024];
int len;
while ((len = reader.read(buffer)) != -1) {
msg.append(buffer, 0, len);
}
System.out.println("Received: " + msg);
// 回複確認
writer.write("ACK: " + msg);
writer.flush();
} catch (IOException e) {
System.err.println("Client handling error: " + e.getMessage());
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
關鍵點:
server.accept() 阻塞直到客戶端連接,返回專用 Socketread() == -1(對端關閉輸出流)
public class EchoServer {
public static void main(String[] args) {
try (ServerSocket server = new ServerSocket(9999)) {
System.out.println("Echo server started on 9999");
while (true) {
try (Socket client = server.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
PrintWriter out = new PrintWriter(client.getOutputStream(), true)) { // autoFlush=true
String line;
while ((line = in.readLine()) != null) {
System.out.println("Echo: " + line);
out.println(line); // 原樣返回
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
測試:
$ telnet 127.0.0.1 9999
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
hello
hello
world
world
ExecutorService pool = Executors.newFixedThreadPool(50);
while (true) {
Socket client = server.accept();
pool.submit(() -> handleClient(client));
}
ServerSocketChannel + Selector 多路複用TCP 無消息邊界,必須自定義協議:
// 客戶端半關閉:發送完數據關閉輸出流,但保留輸入流接收響應
socket.shutdownOutput();
// 服務端檢測到 read() == -1 後處理完業務再關閉
| 現象 | 原因 | 解決 |
|---|---|---|
| 客戶端發送無響應 | 未 flush / 服務端未讀取完 | 檢查 flush、協議解析邏輯 |
| 中文亂碼 | 編解碼字符集不一致 | 統一指定 UTF-8 |
| 連接重置 | 對端異常關閉 / 防火牆 | 捕獲 ConnectionResetException |
| 地址已在使用 | 端口未釋放 / TIME_WAIT | SO_REUSEADDR / 等待 2MSL |
java.net 標準 API,避免 sun.* 內部類socket.setSoTimeout(ms)TCP_NODELAY(關閉 Nagle 算法)降低小包延遲setSendBufferSize/setReceiveBufferSizeSocket 編程是分布式系統的基石。掌握 TCP 流式通信本質、協議設計原則、同步阻塞與異步非阻塞模型,配合 Netty 等工業級框架,即可構建高性能、可靠的網絡應用。建議進階學習:SSL/TLS 安全通信、HTTP/2 多路複用、gRPC RPC 框架、WebSocket 全雙工通信。