手写一个简易版的 Tomcat(迷你 Web 服务器),核心是理解 Tomcat 的底层原理:基于 Socket 监听 HTTP 请求、解析请求报文、处理请求并返回 HTTP 响应。下面我会分步骤拆解实现思路,并提供可运行的核心代码,帮你从 0 到 1 实现一个最小可用的迷你 Tomcat。
一、核心原理梳理
Tomcat 本质是一个「HTTP 协议的 Socket 服务器」,核心能力包括:
- 监听指定端口(如 8080),等待客户端(浏览器)连接;
- 接收并解析 HTTP 请求报文(请求行、请求头、请求体);
- 根据请求路径(如
/hello)匹配对应的处理逻辑(Servlet 思想); - 构造 HTTP 响应报文,返回给客户端。
二、手写迷你 Tomcat 步骤(附代码)
步骤 1:定义核心接口(模拟 Servlet 规范)
Servlet 是 Tomcat 处理请求的核心组件,我们先定义一个极简的 Servlet 接口,约定处理请求的规范:
java
运行
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
// 模拟 Servlet 接口,定义处理请求的标准
public interface Servlet {
// 初始化方法(Tomcat 启动时执行)
void init();
// 处理请求的核心方法
void service(InputStream inputStream, OutputStream outputStream) throws IOException;
// 销毁方法(Tomcat 关闭时执行)
void destroy();
}
步骤 2:实现具体的 Servlet(业务处理逻辑)
编写一个具体的 HelloServlet,处理 /hello 路径的请求:
java
运行
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
// 具体的业务处理类,对应 /hello 请求
public class HelloServlet implements Servlet {
@Override
public void init() {
// 初始化逻辑(如加载配置、连接数据库)
System.out.println("HelloServlet 初始化完成");
}
@Override
public void service(InputStream inputStream, OutputStream outputStream) throws IOException {
// 1. 构造 HTTP 响应头(必须符合 HTTP 协议规范)
String responseHeader = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html;charset=UTF-8\r\n" +
"\r\n"; // 空行分隔响应头和响应体
// 2. 构造响应体(返回给浏览器的内容)
String responseBody = "<html><head><title>迷你Tomcat</title></head>" +
"<body><h1>Hello Mini Tomcat!</h1><p>请求处理成功</p></body></html>";
// 3. 拼接响应报文并返回
String response = responseHeader + responseBody;
outputStream.write(response.getBytes("UTF-8"));
outputStream.flush();
}
@Override
public void destroy() {
// 销毁逻辑(如释放资源)
System.out.println("HelloServlet 销毁");
}
}
步骤 3:实现核心的 Tomcat 服务器(Socket 监听 + 请求分发)
这是最核心的部分,负责监听端口、解析请求、匹配 Servlet 并处理:
java
运行
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
public class MiniTomcat {
// 1. 定义端口(默认 8080)
private int port = 8080;
// 2. 存储 路径 -> Servlet 的映射(模拟 web.xml 配置)
private Map<String, Servlet> servletMapping = new HashMap<>();
// 初始化 Servlet 映射(模拟 Tomcat 加载 web.xml)
public void initServletMapping() {
// 注册 /hello 路径对应的 HelloServlet
HelloServlet helloServlet = new HelloServlet();
helloServlet.init(); // 初始化 Servlet
servletMapping.put("/hello", helloServlet);
}
// 启动 Tomcat 核心方法
public void start() {
// 1. 初始化 Servlet 映射
initServletMapping();
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("===== 迷你 Tomcat 启动成功,监听端口:" + port + " =====");
// 2. 无限循环监听客户端连接(核心:Socket 阻塞等待)
while (true) {
// 等待浏览器连接,accept() 是阻塞方法
Socket socket = serverSocket.accept();
// 3. 处理请求(每个请求用独立线程,避免阻塞)
new Thread(() -> handleRequest(socket)).start();
}
} catch (IOException e) {
System.err.println("Tomcat 启动失败:" + e.getMessage());
}
}
// 处理单个 HTTP 请求
private void handleRequest(Socket socket) {
try (
InputStream inputStream = socket.getInputStream();
// 输出流用于返回响应,这里省略声明,直接用 socket.getOutputStream()
) {
// 1. 解析 HTTP 请求行,获取请求路径(极简版解析,仅处理 GET 请求)
String requestPath = parseRequestPath(inputStream);
System.out.println("收到请求:" + requestPath);
// 2. 根据请求路径匹配 Servlet
Servlet servlet = servletMapping.get(requestPath);
if (servlet != null) {
// 3. 调用 Servlet 的 service 方法处理请求
servlet.service(inputStream, socket.getOutputStream());
} else {
// 4. 未匹配到 Servlet,返回 404 响应
String notFoundResponse = "HTTP/1.1 404 Not Found\r\n" +
"Content-Type: text/html;charset=UTF-8\r\n" +
"\r\n" +
"<h1>404 - 页面不存在</h1>";
socket.getOutputStream().write(notFoundResponse.getBytes("UTF-8"));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close(); // 关闭连接
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 极简版 HTTP 请求解析:仅提取请求行中的路径(如 GET /hello HTTP/1.1 → /hello)
private String parseRequestPath(InputStream inputStream) throws IOException {
byte[] buffer = new byte[1024];
int len = inputStream.read(buffer);
if (len <= 0) return "/";
String requestContent = new String(buffer, 0, len);
// 按换行拆分请求行(第一行是请求行)
String[] lines = requestContent.split("\r\n");
if (lines.length == 0) return "/";
// 拆分请求行:GET /hello HTTP/1.1 → ["GET", "/hello", "HTTP/1.1"]
String[] requestLine = lines[0].split(" ");
return requestLine.length >= 2 ? requestLine[1] : "/";
}
// 主方法启动 Tomcat
public static void main(String[] args) {
new MiniTomcat().start();
}
}
步骤 4:测试运行
- 把上述 3 个类放在同一个 Java 项目中;
- 运行
MiniTomcat的main方法,控制台会输出:===== 迷你 Tomcat 启动成功,监听端口:8080 =====; - 打开浏览器,访问
http://localhost:8080/hello,会看到「Hello Mini Tomcat!」的页面; - 访问
http://localhost:8080/xxx(不存在的路径),会看到 404 页面。
三、进阶扩展(可选,贴近真实 Tomcat)
上述是最小可用版,真实 Tomcat 还包含更多核心能力,可逐步扩展:
- 完善 HTTP 解析:解析请求头(如 Content-Type)、请求体(POST 参数);
- Servlet 生命周期管理:通过反射动态加载 Servlet(而非硬编码);
- 上下文(Context)管理:支持多 Web 应用(多上下文);
- 线程池:用线程池替代单个线程,提高并发处理能力;
- 静态资源处理:支持返回 HTML/CSS/JS 等静态文件;
- 异常处理:统一处理 Servlet 执行异常,返回 500 响应。
总结
手写迷你 Tomcat 的核心关键点:
- 底层基石 :基于
ServerSocket监听端口,Socket处理客户端连接,这是 Tomcat 作为服务器的基础; - 协议解析:遵循 HTTP 协议规范解析请求报文、构造响应报文(必须包含状态行、响应头、空行、响应体);
- 核心思想:通过「路径 - Servlet 映射」实现请求分发,体现 Tomcat 的 Servlet 容器核心逻辑。