套接字為兩台計算機之間的通信提供了一種機制。在 Java 語言出現之前,套接字已廣泛應用於網絡編程;Java 將底層作業系統的複雜細節封裝起來,讓開發者能輕鬆高效地使用套接字進行網絡通信。
這是一種經典的應用程序開發框架,將數據的表示與其內部處理、存儲分離。就像你在餐廳點披薩:你(客戶端)發送請求,廚房(服務端)處理並制作,服務員(網絡通信)將成品送達。你無需關心廚房內部細節,只需等待結果。一個服務端可以同時為多個客戶端服務,且兩者不一定是不同的硬體設備,也可以是同一台機器上的不同進程。
計算機通信同人類對話一樣,需要遵循規則(協議)。常見協議包括 TCP/IP、UDP 等。Java 提供豐富的網絡類庫,支持兩種通信工具:基於 UDP 的數據報和基於 TCP/IP 的 Socket。TCP 提供可靠、有序、點對點的字節流通道,適合大多數應用場景;UDP 則不保證送達,僅適合少量、短距離、非關鍵數據傳輸。
Java 通過 java.net.Socket(客戶端套接字)和 java.net.ServerSocket(服務端監聽套接字)封裝網絡連接。Socket 內部使用輸入/輸出流進行數據讀寫,屏蔽了 TCP/IP 協議棧的封包、解包等底層細節,這也是它被稱為「流套接字」的原因。可以把 Socket 想象成電話聽筒:雙方建立連接後,通過專用通道對話,掛斷前線路始終佔用。
TCP 連接需要兩個關鍵信息:IP 地址(32 位邏輯地址,如 192.168.0.110)和端口號(標識具體服務,如 HTTP 默認 80,FTP 21)。域名系統(DNS)負責將域名解析為 IP 地址,方便用戶記憶。
以下示例展示如何編寫一個請求遠程文件內容的客戶端:
import java.io.*;
import java.net.*;
public class RemoteFileClient {
private BufferedReader socketReader;
private PrintWriter socketWriter;
private String hostIp;
private int hostPort;
public RemoteFileClient(String hostIp, int hostPort) {
this.hostIp = hostIp;
this.hostPort = hostPort;
}
public void setUpConnection() throws IOException {
Socket client = new Socket(hostIp, hostPort);
socketReader = new BufferedReader(new InputStreamReader(client.getInputStream()));
socketWriter = new PrintWriter(client.getOutputStream());
}
public String getFile(String fileName) throws IOException {
StringBuilder content = new StringBuilder();
socketWriter.println(fileName);
socketWriter.flush();
String line;
while ((line = socketReader.readLine()) != null) {
content.append(line).append("\n");
}
return content.toString();
}
public void tearDownConnection() throws IOException {
socketWriter.close();
socketReader.close();
}
public static void main(String[] args) throws IOException {
RemoteFileClient client = new RemoteFileClient("127.0.0.1", 1001);
client.setUpConnection();
System.out.println(client.getFile("RemoteFileServer.java"));
client.tearDownConnection();
}
}
關鍵步驟:1) 用 IP 和端口實例化 Socket;2) 獲取輸入/輸出流並包裝為 BufferedReader/PrintWriter;3) 讀寫數據;4) 關閉流釋放資源。
服務端使用 ServerSocket 監聽端口,接受連接後為每個客戶端創建 Socket 進行通信:
public class RemoteFileServer {
private int listenPort;
public RemoteFileServer(int listenPort) {
this.listenPort = listenPort;
}
public void acceptConnections() throws IOException {
ServerSocket server = new ServerSocket(listenPort);
while (true) {
Socket incoming = server.accept();
handleConnection(incoming);
}
}
private void handleConnection(Socket client) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
PrintWriter out = new PrintWriter(client.getOutputStream());
String filePath = in.readLine();
BufferedReader fileIn = new BufferedReader(new FileReader(filePath));
String line;
while ((line = fileIn.readLine()) != null) {
out.println(line);
}
fileIn.close();
out.close();
in.close();
}
}
注意:此為單線程模型,同一時間只能處理一個請求,`handleConnection` 會阻塞後續連接。
為支持並發,將每個連接交給獨立線程處理:
public class MultiThreadedServer {
public void acceptConnections() throws IOException {
ServerSocket server = new ServerSocket(1001, 5); // backlog=5
while (true) {
Socket client = server.accept();
new Thread(new ConnectionHandler(client)).start();
}
}
}
class ConnectionHandler implements Runnable {
private Socket socket;
public ConnectionHandler(Socket socket) { this.socket = socket; }
public void run() {
try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream())) {
String file = in.readLine();
try (BufferedReader fileIn = new BufferedReader(new FileReader(file))) {
String line;
while ((line = fileIn.readLine()) != null) out.println(line);
}
} catch (IOException e) { e.printStackTrace(); }
}
}
每個連接啟動一個新線程,雖然簡單但頻繁創建/銷毀線程有開銷,且無法限制最大併發數。
預先創建固定數量的工作線程,使用共享隊列管理連接,避免線程頻繁創建,並能控制最大併發數:
public class PooledServer {
private final List pool = Collections.synchronizedList(new LinkedList<>());
private final int maxHandlers;
public PooledServer(int maxHandlers) { this.maxHandlers = maxHandlers; }
public void start() {
for (int i = 0; i < maxHandlers; i++) {
new Thread(new PooledHandler()).start();
}
// accept loop...
}
protected void handleConnection(Socket client) {
synchronized (pool) {
pool.add(client);
pool.notifyAll();
}
}
class PooledHandler implements Runnable {
public void run() {
while (true) {
Socket client;
synchronized (pool) {
while (pool.isEmpty()) {
try { pool.wait(); } catch (InterruptedException ignored) {}
}
client = pool.remove(0);
}
// process client...
}
}
}
}
優勢:線程復用、可控併發、降低 GC 壓力。適用於高併發、長連接場景(如數據庫連接池、Web 服務器)。
Java Socket 編程的核心在於理解客戶端/服務端模型、TCP 流特性及線程模型選擇。從基礎的阻塞 I/O 到多線程、連接池,每一步都在解決性能與擴展性的矛盾。實際項目中,建議優先考慮成熟的網絡框架(如 Netty),但掌握 Socket 基礎對排查問題、優化架構至關重要。