理解 HTTP 協議與 Socket 通訊的最佳方式,莫過於親手實作一個 Web 伺服器。本例展示一個基於 Java Socket 的多執行緒 HTTP 伺服器核心架構,包含請求解析、靜態檔案服務、錯誤處理與資源釋放等關鍵環節,是學習網路程式設計的絕佳教材。
伺服器採用經典的「主執行緒監聽 + 工作執行緒處理」模型,核心類別職責劃分清晰:
ServerSocket 監聽 8000 連接埠,循環 accept() 等待客戶端連線,為每個連線建立 WebThread 處理。public String parse() {
StringBuffer requestStr = new StringBuffer(2048);
byte[] buffer = new byte[2048];
int i = in.read(buffer);
for (int j = 0; j < i; j++) requestStr.append((char) buffer[j]);
return getUri(requestStr.toString());
}
private String getUri(String requestString) {
int index1 = requestString.indexOf(' ');
int index2 = requestString.indexOf(' ', index1 + 1);
return (index1 != -1 && index2 > index1) ? requestString.substring(index1 + 1, index2) : null;
}
簡單粗暴地讀取首包資料,以空格分隔提取 URI(如 /index.html),滿足教學演示需求。生產環境需完整解析 Header、Body 並處理分包/黏包問題。
public void Send(String ref) throws IOException {
File file = new File(WebServer.WEBROOT, ref);
if (file.exists()) {
FileInputStream fis = new FileInputStream(file);
int ch = fis.read(bytes, 0, 2048);
String sBody = new String(bytes, 0, ch); // 注意:此處原碼有 bug,應指定長度
String sendMessage = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: " + ch + "\r\n\r\n" + sBody;
out.write(sendMessage.getBytes());
} else {
String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: 23\r\n\r\n" +
"<h1>File Not Found</h1>";
out.write(errorMessage.getBytes());
}
}
讀取檔案內容構建 200 OK 回應,檔案不存在回 404。注意:new String(bytes,0) 未指定長度會讀取陣列殘留垃圾資料,正確寫法為 new String(bytes, 0, ch)。此外,僅支援 text/html,實際應根據副檔名設定 MIME 類型。
class WebThread extends Thread {
public void run() {
try {
Request rq = new Request(sk.getInputStream());
String sURL = rq.parse();
if (sURL.equals("/")) sURL = WebServer.defaultPage;
new Response(sk.getOutputStream()).Send(sURL);
} finally {
// 關閉 in, out, socket
}
}
}
每個連線獨立一執行緒,互不阻塞,實現併發服務。生產級伺服器會改用執行緒池(ExecutorService)避免執行緒耗盡,並引入 NIO 非阻塞 IO 提升擴展性。
編譯後執行 java WebServer ./webroot 指定靜態資源根目錄。瀏覽器訪問 https://localhost:8000/ 即可看到 index.htm 內容。這個簡易伺服器雖功能有限,但完整展示了 HTTP 伺服器的靈魂:Socket 監聽 → 協議解析 → 資源定位 → 回應構建 → 連線關閉。