【0~1】实现一个精简版的Tomcat服务器

news2024/9/20 15:42:30

真正的勇气,是在知道生活的真相之后,依然热爱生活。

《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 的启动流程主要包括以下步骤:

  1. 加载配置文件:Tomcat 的启动过程首先会加载 server.xml 配置文件,该文件定义了 Connector、Engine、Host、Context 等各个组件的配置信息。
  2. 初始化 Server:Tomcat 的主类 org.apache.catalina.startup.Bootstrap 启动,创建 Server 实例。Server 负责管理整个服务器的生命周期。
  3. 初始化 Service:Server 启动时,会初始化一个或多个 Service,每个 Service 包含一个 Engine 和多个 Connector。
  4. 初始化 Connector 和 Engine:每个 Connector 会初始化一个网络端口,并开始监听客户端请求。同时,Engine 负责请求的实际处理,将请求路由到正确的 Host 和 Context。
  5. 启动连接器并开始监听:当所有组件初始化完毕后,Tomcat 开始监听端口,准备处理客户端的请求。

Tomcat 的请求处理流程

Tomcat 的请求处理流程分为以下几个主要步骤:

  1. 接收 HTTP 请求:当用户发送 HTTP 请求时,Tomcat 的 Connector 负责监听指定端口,并将接收到的请求转换为 Request 和 Response 对象。Coyote 组件 作为 Tomcat 的 HTTP 连接器,负责解析 HTTP 请求,并封装成 HttpServletRequest 和 HttpServletResponse 对象,传递给 Engine。
  2. 通过 Engine 处理请求:Engine 接收请求后,会根据请求的 URL 查找对应的 Host。
  3. 选择合适的 Host:Engine 会根据请求的域名(如 www.example.com),选择对应的 Host 进行处理。如果匹配失败,会返回默认的 Host。
  4. 定位到 Context:Host 再根据 URL 的路径部分,选择合适的 Context 处理请求。Context 通常对应一个 Web 应用(如 /app),代表 Servlet 容器的工作环境。
  5. 选择合适的 Servlet:Context 根据 web.xml 或 注解 中定义的 URL 模式(如 /hello),将请求路由到具体的 Servlet。
  6. Servlet 处理请求:Tomcat 调用 Servlet 的 service() 方法,处理请求逻辑。根据请求的 HTTP 方法,service() 方法会进一步调用 doGet()、doPost() 等具体的处理方法。
  7. 生成 HTTP 响应:Servlet 处理完业务逻辑后,将结果写入 HttpServletResponse 对象中,Connector 负责将响应返回给客户端。

Pipeline 与 Valve 的处理流程

Tomcat 的请求处理流程中,Pipeline 和 Valve 机制用于在请求和响应之间添加多个处理步骤。这种设计模式类似于拦截器链,每个 Valve 可以对请求或响应进行拦截、修改或监控。

  1. Pipeline:每个 Container(例如 Engine、Host、Context)都有一个 Pipeline,其内部包含多个 Valve。Pipeline 负责将请求从一个 Valve 传递到下一个 Valve。
  2. 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2139920.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一次RPC调用过程是怎么样的?

注册中心 RPC&#xff08;Remote Procedure Call&#xff09;翻译成中文就是 {远程过程调用}。RPC 框架起到的作用就是为了实现&#xff0c;调用远程方法时&#xff0c;能够做到和调用本地方法一样&#xff0c;让开发人员更专注于业务开发&#xff0c;不用去考虑网络编程等细节…

【开源免费】基于SpringBoot+Vue.JS企业客户管理系统(JAVA毕业设计)

本文项目编号 T 036 &#xff0c;文末自助获取源码 \color{red}{T036&#xff0c;文末自助获取源码} T036&#xff0c;文末自助获取源码 目录 一、系统介绍1.1 管理员角色1.2 普通员工角色1.3 系统特点 二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内…

苹果手机备份照片怎么删除

在数字时代&#xff0c;备份照片是保护我们珍贵记忆不受意外丢失影响的一种重要方式。苹果手机用户通常利用iCloud或iTunes来备份他们的照片&#xff0c;确保数据的安全。然而&#xff0c;随着时间的推移&#xff0c;这些备份可能会积累大量不再需要的照片&#xff0c;占用宝贵…

鸿蒙开发之ArkTS 基础二

ArkTS常用的基础数据类型 1.字符串 关键字是string 2.数字 关键字是number 3.布尔 关键字是boolean 语法格式是:let 变量名:变量类型 变量值 其中let是关键表示变量&#xff0c;可以修改&#xff0c;可以改变一只对应的是const 修饰&#xff0c;常量不能修改&#xff0c;…

Python画笔案例-050 绘制天空之眼

1、绘制天空之眼 通过 python 的turtle 库绘制 天空之眼&#xff0c;如下图&#xff1a; 2、实现代码 绘制 天空之眼&#xff0c;以下为实现代码&#xff1a; """天空之眼.py """ import math import turtledef draw_square(length,level):if l…

idea同时装了两个版本,每次打开低版本都需要重新激活破解

问题描述&#xff1a; idea同时装了两个版本&#xff0c;每次打开低版本都需要重新激活破解。低版本是2021.1&#xff0c;高版本是2023.1 解决方案&#xff1a; 找到idea的配置路径&#xff0c;比如我的是&#xff1a;C:\Users\Administrator\AppData\Roaming\JetBrains 2021…

【我要成为配环境高手】Nodejs安装与配置

文章目录 1.nodejs安装2.配置npm的全局安装路径3.切换npm的淘宝镜像4.安装vue-cli 1.nodejs安装 从官网下载安装LTS版本的nodejs nodejs会自动安装环境变量&#xff0c;因此安装完成后直接在cmd中查看node版本 node -v2.配置npm的全局安装路径 以管理员身份运行cmd&#xff…

office 2021安装教程

软件介绍 Microsoft Office是微软公司开发的一套基于 Windows 操作系统的办公软件套装。常用组件有 Word、Excel、Powerpoint等。该软件最初出现于九十年代早期&#xff0c;最初是一个推广名称&#xff0c;指一些以前曾单独发售的软件的合集。当时主要的推广重点是购买合集比单…

matlab边缘点提取函数

1、边缘提取 matlab自带点云边缘提取函数,用于搜索点云边界,其核心是alpha shapes算法。alpha shapes提取边缘点,主要是依据滚动圆绕点云进行旋转,实现边缘检测,原理如下图所示。具体原理及效果,可以参考之前我写的博客:基于alpha shapes的边缘点提取(matlab)-CSDN博客…

实习项目|苍穹外卖|day10

Spring Task cron 表达式 入门案例 订单状态定时处理 通知用户支付&#xff01;通知商家完成订单&#xff01; Scheduled(cron "0 0/1 * * * ? ")public void processTimeoutOrder(){log.info("定时处理超时订单: {}", LocalDateTime.now());//答案是…

黑马程序员Java笔记整理(day01)

1.windowsR进入运行&#xff0c;输入cmd 2.环境变量 3.编写java第一步 4.使用idea 5.注释 6.字面量 7.变量 8.二进制 9.数据类型 10.关键词与标识符

仿真软件PROTEUS DESIGN SUITE遇到的一些问题

仿真软件PROTEUS DESIGN SUITE遇到的一些问题 软件网上有很多下载地址自己找哈! 首先如果遇到仿真 没有库 ,需要在网上下载库文件替换到DATA目录下 如果不是默认安装到C盘需要手动修改这些地址,不然会报错!! 当遇到点击仿真出现报错 : 检查这个设置地址是否正确: 随便在库文…

Unity3D 小案例 像素贪吃蛇 02 蛇的觅食

Unity3D 小案例 像素贪吃蛇 第二期 蛇的觅食 像素贪吃蛇 食物生成 在场景中创建一个 2D 正方形&#xff0c;调整颜色&#xff0c;添加 Tag 并修改为 Food。 然后拖拽到 Assets 文件夹中变成预制体。 创建食物管理器 FoodManager.cs&#xff0c;添加单例&#xff0c;可以设置…

周期冲激函数

指数函数的求和----真周期冲击 指数函数有限积分----假单个冲击 指数函数无限积分----真单个冲击

职业院校数据科学与大数据技术专业人工智能实训室建设方案

一、引言 随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;其在全球范围内的应用日益广泛&#xff0c;从智能交通、环境保护到公共安全、智能家居等多个领域均展现出巨大的潜力。然而&#xff0c;我国在人工智能领域的人才储备仍显不足&#xff0c;这已成为…

8. 尝试微调LLM大型语言模型,让它会写唐诗

这篇文章与3. 进阶指南&#xff1a;自定义 Prompt 提升大模型解题能力一样&#xff0c;本质上是专注于“用”而非“写”&#xff0c;你可以像之前一样&#xff0c;对整体的流程有了一个了解&#xff0c;尝试调整超参数部分来查看对微调的影响。 这里同样是生成式人工智能导论&a…

华为HarmonyOS地图服务 -- 三种地图类型 -- HarmonyOS9

一. 场景介绍 Map Kit支持以下地图类型&#xff1a; STANDARD&#xff1a;标准地图&#xff0c;展示道路、建筑物以及河流等重要的自然特征。NONE&#xff1a;空地图&#xff0c;没有加载任何数据的地图。TERRAIN&#xff1a;地形图。 1 标准地图&#xff1a; …

7.1溪降技术:徒步

目录 7.1 徒步运动概述观看视频课程电子书&#xff1a;徒步路线选择故事时间不稳定地形 7.1 徒步 运动概述 徒步是溪降活动中不可或缺的一部分&#xff0c;我们在下降峡谷时大部分时间都在徒步。随着我们进入更具挑战性的峡谷&#xff0c;能够高效移动将使我们更加自信和安全。…

Semaphore UI --Ansible webui

1、安装python python下载地址 https://www.python.org/downloads/ 选好版本下载 wget https://www.python.org/ftp/python/3.11.9/Python-3.11.9.tar.xz安装编译工具 sudo dnf groupinstall "Development Tools"安装依赖包 dnf install bzip2-devel ncurses-deve…

react18基础教程系列-- 框架基础理论知识mvc/jsx/createRoot

react的设计模式 React 是 mvc 体系&#xff0c;vue 是 mvvm 体系 mvc: model(数据)-view(视图)-controller(控制器) 我们需要按照专业的语法去构建 app 页面&#xff0c;react 使用的是 jsx 语法构建数据层&#xff0c;需要动态处理的的数据都要数据层支持控制层: 当我们需要…