在 Java 網路程式設計中,Socket 和 ServerSocket 是兩個核心類別。ServerSocket 負責在伺服器端監聽特定埠號,當客戶端發起連線請求時,透過 accept() 方法建立連線,並產生一個對應的 Socket 實例雙向通訊。這樣雙方各持有一個 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[] buffer = new char[64];
int len;
StringBuilder sb = new StringBuilder();
while ((len = reader.read(buffer)) != -1) {
sb.append(new String(buffer, 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(); // 必須 flush 確保資料送出
writer.close();
client.close();
}
}
關鍵提醒:若寫入後不關閉流,而是進行阻塞式讀取,務必呼叫 flush(),否則雙方可能陷入互相等待的死結。
Socket 本質是全雙工的,但若伺服器端先阻塞讀取,再嘗試寫入回應,會導致寫入程式碼永遠無法執行。解決方案是約定「結束標記」(如 eof),讀取端偵測到標記即跳出迴圈,繼續後續邏輯。
while ((len = reader.read(chars)) != -1) {
String temp = new String(chars, 0, len);
if (temp.indexOf("eof") != -1) {
sb.append(temp.substring(0, temp.indexOf("eof")));
break;
}
sb.append(temp);
}
Writer writer = new OutputStreamWriter(socket.getOutputStream());
writer.write("Hello Client.");
writer.flush();
writer.write("Hello Server.");
writer.write("eof");
writer.flush();
// 然後進行讀取...
單執行緒伺服器一次只能服務一個客戶端。引入多線程可實現併發:主線程循環 accept(),每新增一個連線即啟動新執行緒處理該 Socket。
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() {
// 處理讀寫邏輯,同上述雙向通訊
}
}
BufferedReader.readLine() 以換行符為邊界讀取,適合行導向協議。注意:發送端必須發送換行符(\n)並 flush(),否則接收端會持續阻塞。
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
if (line.contains("eof")) break;
// 處理每一行
}
透過 socket.setSoTimeout(毫秒) 設定讀取超時。超過時間未收到資料會拋出 SocketTimeoutException,適用於同步請求場景。
client.setSoTimeout(10 * 1000); // 10 秒
try {
while ((line = br.readLine()) != null) { ... }
} catch (SocketTimeoutException e) {
System.out.println("資料讀取超時");
}
傳輸雙方必須使用相同編碼。例如伺服器端以 GBK 讀取、UTF-8 寫入,客戶端則須對應 GBK 寫入、UTF-8 讀取,否則中文會出現亂碼。
// 伺服器端指定編碼
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "GBK"));
Writer writer = new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
// 客戶端對應調整
Writer writer = new OutputStreamWriter(client.getOutputStream(), "GBK");
BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
Java Socket 程式設計涵蓋連線建立、阻塞 I/O 處理、協議設計(結束標記/換行符)、多線程併發、超時控制與編碼一致性。掌握這些核心要點,即可構建穩健的網路應用。