Java Socket 編程圍繞兩大核心類展開:ServerSocket(服務端監聽)與 Socket(雙向通信端點)。服務端通過 ServerSocket 綁定端口監聽,調用 accept() 阻塞等待客戶端連接;連接建立後,雙方各持一個 Socket,通過其輸入/輸出流實現全雙工通信。
public class Server {
public static void main(String[] args) throws IOException {
int port = 8899;
ServerSocket server = new ServerSocket(port);
Socket socket = server.accept(); // 阻塞等待連接
Reader reader = new InputStreamReader(socket.getInputStream());
char[] chars = new char[64];
int len;
StringBuilder sb = new StringBuilder();
while ((len = reader.read(chars)) != -1) {
sb.append(new String(chars, 0, len));
}
System.out.println("from client: " + sb);
reader.close();
socket.close();
server.close();
}
}
public class Client {
public static void main(String[] args) throws Exception {
String host = "127.0.0.1";
int port = 8899;
Socket client = new Socket(host, port);
Writer writer = new OutputStreamWriter(client.getOutputStream());
writer.write("Hello Server.");
writer.flush(); // 關鍵:刷新緩衝區確保數據發送
writer.close();
client.close();
}
}
關鍵點:若寫操作後不關閉流而是進行其他阻塞操作,必須調用 flush(),否則會導致雙方無限等待。
Socket 原生支持雙向通信,但流的阻塞特性要求雙方約定終止標記(如 "eof"),否則 read() 會永久阻塞導致後續邏輯無法執行。
while ((len = reader.read(chars)) != -1) {
String temp = new String(chars, 0, len);
if ((index = temp.indexOf("eof")) != -1) {
sb.append(temp.substring(0, index));
break; // 遇到終止標記跳出循環
}
sb.append(temp);
}
// 後續可繼續發送響應...
writer.write("Hello Client.");
writer.write("eof"); // 回應終止標記
writer.flush();
writer.write("Hello Server.");
writer.write("eof"); // 發送終止標記
writer.flush();
// 讀取服務端響應
Reader reader = new InputStreamReader(client.getInputStream());
while ((len = reader.read(chars)) != -1) {
String temp = new String(chars, 0, len);
if ((index = temp.indexOf("eof")) != -1) {
sb.append(temp.substring(0, index));
break;
}
sb.append(temp);
}
服務端通過 while(true) 循環不斷 accept(),但同一時刻只能處理一個客戶端,併發性能差。
while (true) {
Socket socket = server.accept();
new Thread(new Task(socket)).start(); // 每連接啟動獨立線程
}
static class Task implements Runnable {
private Socket socket;
public Task(Socket socket) { this.socket = socket; }
public void run() {
try { handleSocket(); }
catch (Exception e) { e.printStackTrace(); }
}
private void handleSocket() throws Exception {
// 具體通信邏輯...
}
}
實現了併發處理,但線程創建銷毀開銷大,高併發下易耗盡資源。
利用 BufferedReader.readLine() 替換字符數組讀取,自動識別換行符(\n/\r\n)或流結束符。注意:寫入端必須發送換行符並 flush(),否則 readLine() 永久阻塞。
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
String temp;
while ((temp = br.readLine()) != null) {
if (temp.indexOf("eof") != -1) break;
// 處理邏輯...
}
writer.write("Hello Client.\n"); // 必須包含換行符
writer.write("eof\n");
writer.flush();
Socket.setSoTimeout(毫秒) 設置讀操作超時時間。超過指定時間未讀到數據將拋出 SocketTimeoutException,適用於同步調用場景的熔斷保護。
Socket client = new Socket(host, port);
client.setSoTimeout(10 * 1000); // 10秒超時
BufferedReader br = new BufferedReader(
new InputStreamReader(client.getInputStream()));
try {
while ((temp = br.readLine()) != null) { /* ... */ }
} catch (SocketTimeoutException e) {
System.out.println("數據讀取超時。");
}
亂碼本質是發送端編碼與接收端解碼不一致。雙方必須顯式指定相同字符集(如 UTF-8 或 GBK)。
// 輸入流用 GBK 讀取
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "GBK"));
// 輸出流用 UTF-8 發送
Writer writer = new OutputStreamWriter(
socket.getOutputStream(), "UTF-8");
// 發送用 GBK
Writer writer = new OutputStreamWriter(
client.getOutputStream(), "GBK");
// 接收用 UTF-8
BufferedReader br = new BufferedReader(
new InputStreamReader(client.getInputStream(), "UTF-8"));
生產環境建議統一使用 UTF-8,並在協議層面明確約定編碼標準。