题目
实现一个支持多线程的简易HTTP服务器
信息
- 类型:问答
- 难度:⭐⭐
考点
Socket编程,HTTP协议理解,多线程处理,资源管理
快速回答
实现要点:
- 使用
ServerSocket监听端口,循环接受客户端连接 - 为每个连接创建独立线程处理请求
- 解析HTTP请求行(GET /path HTTP/1.1)
- 根据请求路径返回静态资源或404响应
- 正确设置响应头(Content-Type, Content-Length)
- 使用线程池优化线程管理
- 确保资源关闭(try-with-resources)
原理说明
HTTP服务器基于TCP协议,通过Socket建立连接。核心流程:
- 创建ServerSocket绑定端口
- 循环调用accept()接收客户端连接
- 为每个Socket创建独立线程处理请求响应
- 解析HTTP请求报文(重点关注请求行)
- 根据请求路径查找资源并生成响应
- 遵循HTTP协议格式返回响应
代码示例
import java.net.*;
import java.io.*;
import java.util.concurrent.*;
public class SimpleHttpServer {
private static final int PORT = 8080;
private static final int THREAD_POOL_SIZE = 10;
private static final String DOC_ROOT = "./resources";
public static void main(String[] args) throws IOException {
ExecutorService pool = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("Server started on port " + PORT);
while (true) {
Socket clientSocket = serverSocket.accept();
pool.execute(() -> handleRequest(clientSocket));
}
}
}
private static void handleRequest(Socket clientSocket) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
OutputStream out = clientSocket.getOutputStream()) {
// 解析请求行
String requestLine = in.readLine();
if (requestLine == null) return;
String[] parts = requestLine.split(" ");
if (parts.length < 3) return;
String method = parts[0], path = parts[1];
// 处理GET请求
if ("GET".equals(method)) {
File file = new File(DOC_ROOT + path);
if (file.exists() && !file.isDirectory()) {
sendResponse(out, "200 OK", "text/html", Files.readAllBytes(file.toPath()));
} else {
sendResponse(out, "404 Not Found", "text/plain", "Resource not found".getBytes());
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void sendResponse(OutputStream out, String status,
String contentType, byte[] content) throws IOException {
String header = "HTTP/1.1 " + status + "\r\n" +
"Content-Type: " + contentType + "; charset=utf-8\r\n" +
"Content-Length: " + content.length + "\r\n\r\n";
out.write(header.getBytes());
out.write(content);
out.flush();
}
}最佳实践
- 线程池管理:避免线程频繁创建销毁,使用固定大小线程池
- 资源关闭:使用try-with-resources确保Socket/流正确关闭
- 响应头规范:正确设置Content-Type和Content-Length
- 路径安全:验证文件路径防止路径遍历攻击(如检查绝对路径)
- 异常处理:捕获IOException并记录日志,避免服务器崩溃
常见错误
- 资源泄漏:未关闭Socket或流导致文件描述符耗尽
- 阻塞主线程:在accept循环中直接处理请求导致无法接收新连接
- 编码问题:响应未指定charset导致中文乱码
- 并发瓶颈:无限制创建线程(应使用线程池)
- 协议错误:响应头缺少空行分隔header/body,或未遵循HTTP格式
扩展知识
- NIO非阻塞IO:使用Selector实现单线程处理多连接(java.nio包)
- 高性能框架:Netty/Mina等网络框架处理底层细节
- Keep-Alive:通过Connection头实现TCP连接复用
- HTTPS支持:使用SSLServerSocket实现加密通信
- HTTP/2:二进制分帧、头部压缩等高级特性