真正的勇气,是在知道生活的真相之后,依然热爱生活。
《To Kill a Mockingbird》
01 Tomcat 介绍
Tomcat 是一个开源的 Java 应用服务器,主要用来运行基于 Servlet 和 JSP 技术的 Web 应用。Tomcat 实现了 Servlet 规范和 JSP 规范,因此它可以理解和处理 Servlet 和 JSP 的请求。
Tomcat 的架构组件
Tomcat 的架构包含以下几个核心组件:
- Connector(连接器):负责网络通信,接收 HTTP 请求,并将请求转发给 Engine 进行处理。Tomcat 支持多种协议的连接器,如 HTTP 和 AJP。典型的 HTTP 连接器使用 Coyote 组件实现。
- Engine(引擎):处理具体的请求,通过 Pipeline 和 Valve 机制对请求进行预处理,然后根据请求的 URL,将其路由到对应的 Host 和 Context。
- Host(主机):代表一个虚拟主机(类似于 Apache HTTP Server 中的虚拟主机),允许一个 Tomcat 实例管理多个不同的站点。每个 Host 可以包含多个 Context。
- Context(上下文):代表一个 Web 应用(通常对应一个 WAR 文件),每个 Context 处理一个 Web 应用的请求。它是 Web 应用的基本单位,也是 Servlet 容器的具体工作区域。
- Servlet:业务逻辑的具体处理者,Tomcat 通过 Servlet 规范对 Web 请求进行处理。每个 Context 中有多个 Servlet 来处理不同路径下的请求。
- Pipeline(管道)与 Valve(阀):Pipeline 是请求处理的责任链,包含多个 Valve。每个 Valve 可以对请求进行处理或过滤,类似于一系列的拦截器。Tomcat 的请求处理过程通过一系列 Valve 进行过滤和拦截,最终进入 Servlet 进行处理。
Tomcat 的启动流程
Tomcat 的启动流程主要包括以下步骤:
- 加载配置文件:Tomcat 的启动过程首先会加载 server.xml 配置文件,该文件定义了 Connector、Engine、Host、Context 等各个组件的配置信息。
- 初始化 Server:Tomcat 的主类 org.apache.catalina.startup.Bootstrap 启动,创建 Server 实例。Server 负责管理整个服务器的生命周期。
- 初始化 Service:Server 启动时,会初始化一个或多个 Service,每个 Service 包含一个 Engine 和多个 Connector。
- 初始化 Connector 和 Engine:每个 Connector 会初始化一个网络端口,并开始监听客户端请求。同时,Engine 负责请求的实际处理,将请求路由到正确的 Host 和 Context。
- 启动连接器并开始监听:当所有组件初始化完毕后,Tomcat 开始监听端口,准备处理客户端的请求。
Tomcat 的请求处理流程
Tomcat 的请求处理流程分为以下几个主要步骤:
- 接收 HTTP 请求:当用户发送 HTTP 请求时,Tomcat 的 Connector 负责监听指定端口,并将接收到的请求转换为 Request 和 Response 对象。Coyote 组件 作为 Tomcat 的 HTTP 连接器,负责解析 HTTP 请求,并封装成 HttpServletRequest 和 HttpServletResponse 对象,传递给 Engine。
- 通过 Engine 处理请求:Engine 接收请求后,会根据请求的 URL 查找对应的 Host。
- 选择合适的 Host:Engine 会根据请求的域名(如 www.example.com),选择对应的 Host 进行处理。如果匹配失败,会返回默认的 Host。
- 定位到 Context:Host 再根据 URL 的路径部分,选择合适的 Context 处理请求。Context 通常对应一个 Web 应用(如 /app),代表 Servlet 容器的工作环境。
- 选择合适的 Servlet:Context 根据 web.xml 或 注解 中定义的 URL 模式(如 /hello),将请求路由到具体的 Servlet。
- Servlet 处理请求:Tomcat 调用 Servlet 的 service() 方法,处理请求逻辑。根据请求的 HTTP 方法,service() 方法会进一步调用 doGet()、doPost() 等具体的处理方法。
- 生成 HTTP 响应:Servlet 处理完业务逻辑后,将结果写入 HttpServletResponse 对象中,Connector 负责将响应返回给客户端。
Pipeline 与 Valve 的处理流程
Tomcat 的请求处理流程中,Pipeline 和 Valve 机制用于在请求和响应之间添加多个处理步骤。这种设计模式类似于拦截器链,每个 Valve 可以对请求或响应进行拦截、修改或监控。
- Pipeline:每个 Container(例如 Engine、Host、Context)都有一个 Pipeline,其内部包含多个 Valve。Pipeline 负责将请求从一个 Valve 传递到下一个 Valve。
- Valve:Valve 是 Pipeline 的处理单元,每个 Valve 都可以对请求和响应进行预处理。例如:
- 安全检查(验证用户权限)
- 记录访问日志
- 压缩响应数据等
在 Valve 处理完后,最后会调用 Servlet 来处理具体的业务逻辑。
Tomcat 的生命周期管理
Tomcat 的各个组件(Server、Service、Connector、Engine、Host、Context)都有自己的生命周期管理,遵循 Lifecycle 接口。典型的生命周期事件包括:
- init():初始化组件。
- start():启动组件,使其开始工作。
- stop():停止组件,释放资源。
- destroy():销毁组件,完全清除其占用的资源。
Tomcat 使用 LifecycleListener 来监听和管理组件的生命周期,当某个组件的状态发生变化时,Tomcat 会通知所有相关的 LifecycleListener,以便进行相应的处理。
线程模型与并发处理
Tomcat 采用线程池来处理多个请求:
- 每当有请求进来时,Connector 会从线程池中获取一个线程处理该请求。
- 处理完请求后,线程会被归还到线程池中,以供下一个请求使用。
这种线程池的机制提升了并发处理能力,使得 Tomcat 可以同时处理大量请求。Tomcat 默认使用 NIO 模型,可以通过 server.xml 中的 Connector 配置文件调整线程池大小和处理模式。
02 实现一个简易的Tomcat
基于 Tomcat 的原理理解和执行流程分析,我们大概能理出这么一条逻辑:Tomcat 启动时注册 Servlet,每个 Servlet 对应一个请求路径;接收请求,获取请求协议内容(方法、路径等等);根据逻辑判断,将请求交给不同的 Servlet 处理;不同的 Servlet 返回不同的结果。
所以一个简易的 Tomcat 服务器代码如下:
定义 servlet,做具体的请求逻辑处理。
import java.io.IOException;
import java.io.OutputStream;
public interface Servlet {
void service(OutputStream output) throws IOException;
}
import java.io.IOException;
import java.io.OutputStream;
public class HelloServlet implements Servlet{
@Override
public void service(OutputStream output) throws IOException {
String response = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html\r\n" +
"\r\n" +
"<h1>Hello from HelloServlet!</h1>";
output.write(response.getBytes());
}
}
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
public class TimeServlet implements Servlet{
@Override
public void service(OutputStream output) throws IOException {
String response = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html\r\n" +
"\r\n" +
"<h1>Current Time: " + new Date() + "</h1>";
output.write(response.getBytes());
}
}
定义 container,对请求做分发,根据条件,不同的请求分发到不同的 servlet 上。
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
public class SimpleServletContainer {
private Map<String, Servlet> servletMapping = new HashMap<>();
// 初始化时将Servlet注册到路径
public SimpleServletContainer() {
servletMapping.put("/hello", new HelloServlet());
servletMapping.put("/time", new TimeServlet());
}
public void handleRequest(String path, OutputStream output) throws IOException {
Servlet servlet = servletMapping.get(path);
if (servlet != null) {
servlet.service(output);
} else {
String response = "HTTP/1.1 404 Not Found\r\n" +
"Content-Type: text/html\r\n" +
"\r\n" +
"<h1>404 Not Found</h1>";
output.write(response.getBytes());
}
}
}
定义 server,用于接收请求,并通过 container 分发请求。
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class SimpleHttpServerWithServlet {
private static final int PORT = 8080;
private static SimpleServletContainer servletContainer = new SimpleServletContainer();
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("Server is listening on port " + PORT);
while (true) {
Socket socket = serverSocket.accept();
handleRequest(socket);
}
} catch (IOException e) {
e.printStackTrace();
}
}
private static void handleRequest(Socket socket) {
try (InputStream input = socket.getInputStream();
OutputStream output = socket.getOutputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input))) {
// 读取请求行
String requestLine = reader.readLine();
System.out.println("Request: " + requestLine);
// 解析HTTP请求方法和路径
String[] requestParts = requestLine.split(" ");
String method = requestParts[0];
String path = requestParts[1];
// 通过Servlet容器处理请求
if (method.equals("GET")) {
servletContainer.handleRequest(path, output);
} else {
String response = "HTTP/1.1 405 Method Not Allowed\r\n" +
"Content-Type: text/html\r\n" +
"\r\n" +
"<h1>405 Method Not Allowed</h1>";
output.write(response.getBytes());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
以上就实现了一个简易的 Tomcat 服务器,能基本实现对 http 请求的处理。
当我们访问 localhost:8080/hello 时,返回
当我们访问 localhost:8080/time 时,返回
当我们用post请求时,直接显示
03 总结
Tomcat 是一个基于 Java 的轻量级应用服务器,通过 Connector 接收请求,通过 Engine、Host 和 Context 来路由请求,最终由 Servlet 处理。整个请求处理过程遵循 Pipeline 和 Valve 的拦截机制,同时 Tomcat 通过线程池和非阻塞 IO 技术来提高并发处理能力。
通过了解 Tomcat 基本原理和执行流程,我们手动实现了一个简易版的 Tomcat 服务器,并成功运行。
希望这篇文章能给到你一些小小的帮助,也不枉你花费时间来阅读~
Peace & Love