当前位置: 首页 > news >正文

手写Tomcat(二)—Tomcat简化模型架构

一、 Tomcat架构

Tomcat的主要角色是 servlet容器,提供一个解释器,能够解析并执行JavaScript Object Notation (JON)脚本(后更改为Servlet),并将请求传送到指定的服务器(如JavaBean)。因此,它是Servlet处理请求、与客户端通信(RESTful API)以及处理服务器端业务逻辑的关键组件。

1.1 HttpServletRequest

HttpServletRequest类在模型中充当 HTTP请求的元数据载体,是连接网络层(Connector)与业务层(Servlet)的桥梁。 

public class HttpServletRequest {private String url;public String method;public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getMethod() {return method;}public void setMethod(String method) {this.method = method;}
}
  • URL路径:标识客户端请求的资源位置(如/login)。

  • HTTP方法:明确操作类型(GET获取、POST提交),用于路由到Servlet的正确处理逻辑。

与实际 Tomcat 实现的差异

简化实现实际 Tomcat 的 HttpServletRequest
仅包含urlmethod字段包含完整的请求信息(头、参数、会话、Cookie等)
直接通过Setter注入数据数据由Tomcat底层协议解析器自动填充
无协议规范校验(如方法合法性)严格校验HTTP协议规范(如方法名有效性)
无线程安全控制每个请求独立实例,天然线程安全

1.2 HttpServletResponse

HttpServletResponse类在模型中充当 响应数据的输出通道,是业务逻辑(Servlet)与网络层(Connector)之间的桥梁。 

核心思想

  • 数据输出本质:所有响应最终通过底层OutputStream发送字节流。

  • 分层设计:业务层无需关心网络细节,只需操作抽象响应对象。

  • 协议封装:实际Tomcat隐藏了HTTP协议拼接的复杂性。

public class HttpServletResponse {//用于持有输出流,通过此流将数据发送到客户端private OutputStream outputStream; //接收一个OutputStream(通常来自服务器Socket连接),以便后续写入响应内容。public HttpServletResponse(OutputStream outputStream){this.outputStream = outputStream;}  //将字符串转换为字节数组并写入输出流。public void write(String context) throws IOException {outputStream.write(context.getBytes());}
}

1. 响应数据输出通道

        持有输出流:通过构造函数接收底层网络输出流(通常来自Tomcat的Connector模块),直接操作字节流。

        数据写入write()方法将字符串转换为字节写入流,完成响应体的发送。

2. 在请求处理流程中的角色

        Connector阶段:由连接器创建实例,并注入与客户端Socket关联的OutputStream

        Container阶段:传递到Servlet的service()方法,供业务逻辑生成响应内容。

3. 基础协议支持

        仅处理响应体:直接写入原始字节流,但未包含HTTP协议头(如状态码、Content-Type)。

与实际 Tomcat 实现的差异

简化实现实际 Tomcat 的 HttpServletResponse
仅支持直接写入响应体支持自动管理状态码、响应头、Cookies等
无编码控制(默认平台编码)提供setCharacterEncoding()setContentType()方法
无缓冲机制(直接写Socket流)内置缓冲区(BufferedOutputStream)提升性能
无重定向/转发支持支持sendRedirect()forward()方法
无错误处理机制可调用sendError()返回标准错误页面

1.3 servlet接口

servlet接口在模型中定义了 请求处理的唯一入口,是Tomcat将网络层(Connector)与业务逻辑(开发者代码)解耦的核心设计。 

核心思想

  • 面向接口编程:Tomcat通过接口调用开发者代码,降低耦合度。

  • 请求-响应范式:所有Web交互抽象为request输入和response输出。

  • 容器管理:Servlet实例的创建、调用、销毁由Tomcat控制,开发者聚焦业务逻辑。

public interface servlet {public void service(HttpServletRequest request, HttpServletResponse response);
}

1. 业务逻辑的统一入口

  • 请求处理抽象化:所有具体业务逻辑(如用户登录、数据查询)必须实现service()方法,Tomcat通过此方法将控制权交给开发者。

  • 协议无关性:开发者无需关心HTTP协议的解析细节(如报文拆分、状态码生成),只需操作requestresponse对象。

2. 在Tomcat处理流程中的角色

  • Container阶段调用:当Tomcat完成URL匹配并确定目标Servlet后,调用其service()方法。

  • 流程控制枢纽:通过service()方法,开发者可以:

    • 读取请求参数(request.getParameter())。

    • 处理业务数据(如访问数据库)。

    • 生成响应内容(response.write())。

3. 设计模式体现

  • 模板方法模式:实际Servlet实现类(如HttpServlet)通过重写service()或具体方法(doGet()/doPost())定义行为。

  • 控制反转(IoC):Tomcat容器控制Servlet生命周期及方法调用,开发者仅需实现接口。

与实际 javax.servlet.Servlet 接口的差异

简化实现实际 Servlet 接口
仅定义service()方法包含init()destroy()等生命周期方法
无配置参数支持可通过ServletConfig获取初始化参数
直接处理HTTP协议通常继承HttpServlet抽象类,实现doGet()等方法
无线程安全约束规范明确Servlet实例默认单例多线程调用,需开发者自行保证线程安全

1.4 HttpServlet

HttpServlet类在模型中实现了 HTTP方法的路由分发,是Tomcat将协议细节与业务逻辑分离的关键设计。 

核心思想

  • 职责分离:协议处理(方法路由)与业务逻辑(doXxx())解耦。

  • 扩展性:通过覆盖特定方法支持新功能,无需修改框架代码。

  • 规范化:统一Servlet实现模式,降低开发者学习成本。

public abstract class HttpServlet implements servlet{//doGet方法public void doGet(HttpServletRequest request,HttpServletResponse response){}//doPost方法public void doPost(HttpServletRequest request,HttpServletResponse response){}//根据请求方式判断调用的方法@Overridepublic void service(HttpServletRequest request, HttpServletResponse response) {if(request.getMethod().equals("GET")){doGet(request,response);}else if(request.getMethod().equals("POST")){doPost(request,response);}}
}

核心作用 

1. HTTP方法的路由分发

  • 请求方法解耦:将不同HTTP方法(GET/POST)的处理逻辑分离到独立方法(doGet()/doPost()),避免在service()中堆积条件判断

  • 开发者友好性:子类只需覆盖关注的方法(如doGet()),无需重写整个service()

2. 在Tomcat处理流程中的角色

  • 容器调用入口:Tomcat调用service()方法后,自动根据请求方法触发对应的doXxx()

  • 模板方法模式:定义算法骨架(方法路由),具体步骤由子类实现。

3. 规范扩展性

  • 支持新HTTP方法:通过扩展service()方法(如添加doPut()),兼容更多HTTP方法。

  • 统一错误处理:可集中处理未实现的方法(如返回405状态码)。

与实际 HttpServlet 的差异

简化实现实际 javax.servlet.http.HttpServlet
仅支持GET/POST方法支持所有HTTP方法(PUT/DELETE/HEAD等)
无HTTP协议版本处理区分HTTP/1.0和HTTP/1.1的语义差异
无默认错误响应自动返回405 Method Not Allowed 或 400 Bad Request
无线程安全提示明确说明Servlet实例需线程安全

1.5 SearchClassUtil

用于获得某一个包下所有类的路径

public class SearchClassUtil {public static List<String> classPaths = new ArrayList<>();public static List<String> searchClass(){//需要扫描的包名String basePath = "com.qcby.myweb";//将获取到的包名转换为路径String classPath = SearchClassUtil.class.getResource("/").getPath();basePath = basePath.replace(".", File.separator);String searchPath = classPath + basePath;doPath(new File(searchPath),classPath);//得到指定包下所有类的绝对路径,利用绝对路径和Java反射机制得到类对象return classPaths;}/*** 该方法会得到所有的类,将类的绝对路径你写入到classPaths中* file*/private static void doPath(File file, String classpath) {if(file.isDirectory()){File[] files = file.listFiles();for(File f1:files){doPath(f1,classpath);}}else{if(file.getName().endsWith(".class")){String path = file.getPath().replace(classpath.replace("/","\\").replaceFirst("\\\\",""),"").replace("\\",".").replace(".class","");classPaths.add(path);}}}public static void main(String[] args) {List<String> classes = SearchClassUtil.searchClass();for(String s:classes){System.out.println(s);}}
}

运行结果

1.6 ResponseUtil

/*** 设置响应头*/
public class ResponseUtil {public  static  final String responseHeader200 = "HTTP/1.1 200 \r\n"+"Content-Type:text/html\r\n"+"\r\n";public static String getResponseHeader404(){return "HTTP/1.1 404 \r\n"+"Content-Type:text/html\r\n"+"\r\n" + "404";}public static String getResponseHeader200(String context){return "HTTP/1.1 200\r\n"+"Content-Type:text/html\r\n"+"\r\n" + context;}}

1.7 XWServlet

核心目标:替代传统的 web.xml 配置文件,通过声明式编程将 Servlet 的 URL 映射路径直接绑定到类上。 

  • URL 路径映射:通过 url 属性指定 Servlet 处理的请求路径(如 /user)。

  • 自动化注册:Tomcat 启动时自动扫描带有该注解的类,并将其注册到容器中。

  • 零配置开发:开发者无需编写 web.xml,直接在代码中声明路由规则。

@Target(ElementType.TYPE)       //仅允许标注在类或接口上
@Retention(RetentionPolicy.RUNTIME)  //确保注解在运行时可通过反射获取,这是Tomcat动态注册 Servlet 的前提
public @interface XWServlet {String url() default "";   //指定 Servlet 的访问路径,默认值为空字符串(需开发者显式配置)
}

1.8 TomcatRoute

public class TomcatRoute {public static HashMap<String, HttpServlet> map = new HashMap<>();static {List<String> paths = PackageUtil.getClassName("com.qcby.myweb");for(String path:paths){try {Class clazz = Class.forName(path);HttpServlet a = (HttpServlet) clazz.getDeclaredConstructor().newInstance();XWServlet xwServlet = (XWServlet) clazz.getDeclaredAnnotation(XWServlet.class);map.put(xwServlet.url(),a);} catch (Exception e) {e.printStackTrace();}}}
}

通过 PackageUtil获取com.qcby.myweb包下的所有类路径,并通过反射创建类对象,通过XWServlet 获取类的url,将url及其对应的类对象放入到HashMap中。

1.9 MyTomcat

public class MyTomcat {static HashMap<String,HttpServlet> map = TomcatRoute.map;public static void dispatch(HttpServletRequest request,HttpServletResponse response){HttpServlet servlet = map.get(request.getUrl());if(servlet != null){servlet.service(request,response);}}public static void start() throws IOException {System.out.println("服务器启动");ServerSocket serverSocket = new ServerSocket(8083);while (true){Socket socket = serverSocket.accept();InputStream inputStream = socket.getInputStream();HttpServletRequest request = new HttpServletRequest();BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));String str = reader.readLine();request.setMethod(str.split(" ")[0]);request.setUrl(str.split(" ")[1]);OutputStream outputStream = socket.getOutputStream();HttpServletResponse response = new HttpServletResponse(outputStream);dispatch(request,response);}}public static void main(String[] args) throws IOException {start();}
}

1.9.1 dispatch 

public static void dispatch(HttpServletRequest request,HttpServletResponse response){HttpServlet servlet = map.get(request.getUrl());if(servlet != null){servlet.service(request,response);}
}

通过request中的url信息查找 其对应的类对象,若存在,则调用service方式。

1.9.2 Socket

在Tomcat中,Socket(套接字)是实现网络通信的核心组件,主要用于处理客户端与服务器之间的数据传输。 

数据从客户端到Tomcat的传递

  1. 客户端发起请求
    客户端通过浏览器发送HTTP请求,数据经操作系统封装为TCP/IP数据包,通过网卡传输到目标服务器。

  2. 网卡接收数据
    服务器的网卡接收到物理信号后,将其转换为二进制数据流,传递给操作系统内核。

  3. Socket监听与处理
    Tomcat的Connector组件通过Endpoint(如NioEndpoint)监听指定端口,接受Socket连接。操作系统将数据流交给Tomcat的Socket实例,由Processor解析HTTP协议并生成Request对象。                                                                                                               注:每个需要网络的进程都要申请端口号,有些端口号是被明确占用的,如Mysql的端口号是3306,HTTP的端口号是8080

  4. Servlet容器处理
    解析后的请求通过Adapter转换为ServletRequest,由Servlet容器(如EngineHostContext)处理业务逻辑,生成响应数据。

  5. 响应返回客户端
    响应数据通过Socket的输出流发送回客户端,经操作系统协议栈封装后,由网卡转换为物理信号传输到客户端。

         System.out.println("服务器启动..."); //1.定义ServerSocket对象进行服务器的端口注册ServerSocket serverSocket = new ServerSocket(8080); //端口号用于区分不同的进程while (true){//2.监听客户端的Socket连接程序Socket socket = serverSocket.accept();  //accept()--》用于阻塞监听,没有程序访问时,停止执行,直到有程序访问时,继续执行下一步//3.从socket对象当中获取到一个字节输入流对象InputStream iStream = socket.getInputStream();HttpServletRequest request = new HttpServletRequest();BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));String str = reader.readLine();request.setMethod(str.split(" ")[0]);request.setUrl(str.split(" ")[1]);OutputStream outputStream = socket.getOutputStream();HttpServletResponse response = new HttpServletResponse(outputStream);dispatch(request,response);}

代码逐行解析 

1. 接受客户端连接

Socket socket = serverSocket.accept();

阻塞等待客户端连接,返回代表该连接的Socket对象。这是建立TCP通信的第一步。

Tomcat对应实现:
Tomcat的Connector组件(如NioEndpoint)负责监听端口,使用NIO多路复用或BIO模型处理连接,而非简单阻塞式accept()。实际会通过线程池高效管理连接。

2. 获取输入流

InputStream inputStream = socket.getInputStream();

获取与客户端Socket关联的输入流,用于读取HTTP请求的原始字节数据。

Tomcat对应实现:
Tomcat的CoyoteAdapter会解析输入流,将其封装为org.apache.coyote.Request对象,处理协议细节(如HTTP头、Chunked编码)。

3. 创建请求对象

HttpServletRequest request = new HttpServletRequest();

实例化一个自定义的请求对象,用于承载解析后的请求信息(如URL、方法)。

Tomcat对应实现:
实际创建的是org.apache.catalina.connector.Request对象,包含完整的请求信息(参数、Session、头信息等),由容器自动填充数据。

4. 包装缓冲读取器

BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

将字节流转换为字符流,便于按行读取请求内容。

潜在问题:
直接使用BufferedReader可能无法正确处理二进制数据(如图片上传)。Tomcat底层使用ByteBuffer进行高效二进制解析。

5. 读取请求行

String str = reader.readLine();

读取HTTP请求的第一行(即请求行),格式为方法 URI 协议版本,例如:
GET /index.html HTTP/1.1

Tomcat对应实现:
通过Http11Processor解析请求行,严格校验协议合规性(如方法名合法性),并提取完整URI和协议版本。

6. 解析HTTP方法与URL

request.setMethod(str.split(" ")[0]);  // 如"GET"
request.setUrl(str.split(" ")[1]);     // 如"/aa.java"

从请求行中提取HTTP方法和请求路径,填充到请求对象中。

简化问题:

未处理URI中的查询参数(如/user?id=1中的id=1)。

未解析协议版本(如HTTP/1.1与HTTP/2差异)。

未处理可能的异常格式(如空路径或非法方法)。

7. 获取输出流

OutputStream outputStream = socket.getOutputStream();

获取与客户端Socket关联的输出流,用于向客户端发送响应数据。

Tomcat对应实现:
输出流会被包装为org.apache.coyote.Response,支持缓冲、分块传输(chunked encoding)和自动压缩(如gzip)。

8. 创建响应对象

HttpServletResponse response = new HttpServletResponse(outputStream);

实例化自定义响应对象,关联输出流以便写入响应内容。

Tomcat对应实现:
实际创建的是org.apache.catalina.connector.Response对象,管理状态码、响应头和内容编码。

9. 分发请求

dispatch(request, response);

将请求和响应对象传递给调度器,执行后续处理(如路由到Servlet或静态资源)。

Tomcat对应实现:

容器层级路由:依次通过Engine→Host→Context→Wrapper,匹配对应的Servlet。

过滤器链执行:在调用Servlet前,执行所有匹配的Filter。

Servlet生命周期:调用service()方法,处理业务逻辑。

请求处理全流程图示

客户端│▼
Socket.accept()            // 建立连接│▼
InputStream → 解析请求行    // 读取HTTP请求基础信息│▼
创建Request/Response对象   // 封装协议数据│▼
dispatch() → 路由到Servlet // 业务逻辑处理│▼
OutputStream → 返回响应     // 发送HTTP响应

相关文章:

  • 第六部分:第六节 - TypeScript 与 NestJS:打造类型安全的厨房管理流程
  • echarts 空心饼图,内说明文字居中
  • 已经 上线 Vue 项目 国际化 i18n 中译英
  • CVE-2022-22947源码分析与漏洞复现
  • Python应用“关键字”初解
  • 车载以太网网络测试-27【SOME/IP-SD简述】
  • MAX96752FGTN/V+T:双LVDS(OLDI)输出的GMSL2解串器架构与应用探讨——汽车与工业视频传输方案深度分析
  • 格雷希尔快速封堵接头,解决新能源汽车的气密性检测和三电系统的综合测试
  • VSCode配置C/C++环境
  • 编程日志5.17
  • MPI中近邻(neighborhood)之间的All-to-All通信
  • Web3.0:下一代互联网的变革与机遇
  • “智”斗秸秆焚烧,考拉悠然以科技之力筑牢生态安全防线
  • AI 招聘系统科普:如何辨别真智能与伪自动化
  • openai-whisper-asr-webservice接入dify
  • 在Ubuntu18.04下搭建SadTalker让图片开口说话
  • Python爬虫实战:研究Crawley 框架相关技术
  • MIPI摄像头linux驱动开发步骤及说明
  • SpringCloud Alibaba微服务-- Sentinel的使用(笔记)
  • 【部署】如何离线环境创建docker容器执行python命令行程序
  • 莱芜话题 莱芜在线/青岛seo青岛黑八网络最强
  • wordpress去category/东莞seo排名公司
  • 网站关键词重要吗/网站优化招聘
  • 租车网站建设/北京网站建设公司优势
  • 什么网站做新闻更好/seo自然排名
  • 深圳网站建设培训/拼多多商品关键词搜索排名