🦊 Tomcat

吞佛童子2022年10月10日
  • frame
  • Tomcat
大约 7 分钟

🦊 Tomcat

1. 概述

1) Tomcat 与其他框架的关系

2) 介绍

  • Tomcat 是 Web应用服务器,是一个 Servlet/JSP 容器.
  • Tomcat 作为 Servlet 容器,负责处理客户请求,把请求传送给 Servlet,并将 Servlet 的响应传送回给客户端

tomcat 10.1 官方文档open in new window

  1. 术语
    • Context:
      • 一个 Context 就是一个 Web 应用程序
  2. 文件结构
    • img.png
    • /bin
      • 启动、关闭、其他脚本文件
      • .sh 文件用于 Unix 系统,.bat 文件用于 Windows 系统,由于 Win32 命令行缺少一些功能,因此文件中还有一些附加文件
      • img_1.png
    • /conf
      • 配置文件
      • 最重要的文件是 server.xml,它是容器的主要配置文件
      • img_2.png
    • /logs
      • 默认情况下,日志文件会存放在此处
    • /webapps
      • 是 webapps 的去处
      • img_3.png
  3. 启动过程
    • startup.sh
    • catalina.sh start
    • java -jar org.apache.catalina.startup.Bootstrap.main()
  4. 核心功能
    • 处理 Socket 连接,负责网络字节流与 Request & Response 对象的转化
    • 加载并管理 Servlet,处理具体的 Request 请求

2. 架构原理

  • Server 对应一个 Tomcat 实例
  • 一个 Tomcat 实例 默认对应 一个 Service
  • 一个 Service 可能有多个连接器,连接不同协议
  • 多个连接器对应一个容器,顶层容器就是 Engine
  • 一个容器 包含 多个 Host
  • 一个 Host 可能有多个 Context 容器
  • 一个 Context 容器可能包含多个 Servlet

3. Connector

1) Tomcat IO 模型

  • Tomcat 支持的 IO 模型有:
    • NIO
      • 同步非阻塞 IO,通过 Java NIO 类库实现
    • NIO2
      • 异步 IO,通过 JDK 7 最新的 NIO2 类库实现
    • APR
      • 通过 Apache 可移植运行库实现,是 C/C++ 编写的本地库

2) Tomcat 应用层协议

  • Tomcat 支持的应用层协议有:
    • img_4.png
    • http 1.1
      • 大部分 Web 应用采用的协议
    • http 2
      • 可提高 Web 性能的协议
    • AJP
      • 用于和 Web 服务器集成,例 Apache

3) 核心组件

① Endpoint

  1. 功能
    • 实现 TCP/IP 协议数据读写
    • 本质是调用操作系统的 socket 接口
  2. 重要组成部分
    • Acceptor
      • 监听 socket 连接请求
      • 循环调用 accept() 接收新连接,一旦有新连接到达,accept() 返回一个 Channel 对象,将其交给 Selector 处理
    • SocketProcessor
      • Selector 发现 Channel数组中有某个 Channel 有数据就绪时,生成一个 SocketProcessor 任务对象给 Executor 处理
      • 处理 Acceptor 接收到的 socket 请求,实现 Runnable 接口,读取 & 解析请求数据

② Processor

  1. 功能
    • 接收 Endpoint 的 socket,读取字节流解析成 Tomcat Request & Response 对象
    • 通过 Adapter 将其提交到 容器 进行处理

③ Adapter

  1. 功能
    • 将传入的 Tomcat Request 对象转换为 ServletRequest,然后才可以传给 容器
    • 将容器传入的 ServletResponse 转换为 Tomcat Response 对象,才可进行后续处理

4) 涉及的设计模式:

  • 模板方法
    • 定义抽象基类 AbstractProtocol,不同应用协议有自己的实现方式
  • 适配器模式
    • Adapter

4. Container

1) 相关容器

  • 一个 Container 只能有 一个 Engine
  • 一个 Engine 引擎 可管理 多个 站点 Host
  • 一个 站点 Host 可部署 多个 Web 应用 Context
  • 一个 Context 可能有 多个 Wrapper
  • 一个 Wrapper 代表 一个 Servlet

2) 涉及的设计模式

  • 组合模式
    • 不同容器之间存在父子关系,因此形成一个树形结构,就是组合模式
    • 每个容器都是一个 接口
    • 所有容器均实现了 Container 接口,组合模式使得用户对 单容器 & 组合容器 对象的使用具有一致性
    • Container 接口部分源码如下:
public interface Container extends Lifecycle {
    /**
     * 获取 当前节点 的名称
     */
    public String getName();

    /**
     * 设置当前节点 名称
     */
    public void setName(String name);

    /**
     * 获取 父容器
     */
    public Container getParent();

    /**
     * 给当前节点设置 父节点
     */
    public void setParent(Container container);
    
    /**
     * 给当前节点 添加子类
     */
    public void addChild(Container child);
    
    /**
     * 通过 名称 查找 子类
     */
    public Container findChild(String name);
    
    /**
     * 返回 子类列表
     */
    public Container[] findChildren();
    
    /**
     * 移除某个 子容器
     */
    public void removeChild(Container child);
  • 观察者模式
    • LifeCycle 设置了事件监听器
    • Lifecycle 部分源码如下:
public interface Lifecycle {
    /**
     * 向当前组件中添加 LifecycleEvent 监听器
     */
    public void addLifecycleListener(LifecycleListener listener);

    /**
     * 获取与当前生命周期绑定的 事件监听器集合
     */
    public LifecycleListener[] findLifecycleListeners();

    /**
     * 移除当前组件绑定的 某个事件监听器
     */
    public void removeLifecycleListener(LifecycleListener listener);

    /**
     * 初始化
     */
    public void init() throws LifecycleException;

    /**
     * 启动
     */
    public void start() throws LifecycleException;

    /**
     * 停止
     */
    public void stop() throws LifecycleException;

    /**
     * 销毁
     */
    public void destroy() throws LifecycleException;

    public LifecycleState getState();

    public String getStateName();

    public interface SingleUse {
    }
}

5. 请求定位 Servlet 的过程

例:客户端访问 url: http://liuxianzhishou.top:9000/log

  1. 根据 协议 & 端口号 确定 Service & Engine
    • 协议为 http协议,因此会被 http 连接器接收该连接请求,一个 连接器 属于 某个 Service 组件,因此 Service 确定
    • 一个 Service 中除了多个 连接器外,还有 一个 Container 容器,一个 Container 容器只能有 一个 Engine,因此 Engine 也确定
  2. 根据 域名 确定 Host
    • 根据域名找到对应的 Host 容器
  3. 根据 url 路径 确定 Context 组件
  4. 根据 url 路径 确定 Wrapper[Servlet]

6. Servlet

类图

  • img.png

1) Servlet 接口

  • 从中可以看出 Servlet 的生命周期:
    • init() 初始化
    • service() 响应请求,进行逻辑处理
    • destroy() 销毁
  • 获取 Servlet 的 相关配置信息 & 自身信息
public interface Servlet {
    /**
     * 1. 初始化
     */
    public void init(ServletConfig config) throws ServletException;
    
    /**
     * 2. 响应请求。
     * 此方法仅在 servlet 的 init() 方法成功完成后调用。
      * Servlet 通常在可以同时处理多个请求的多线程 Servlet 容器中运行,因此需要注意 并发安全
     */
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
    
    /**
     * 3. 由 servlet 容器调用以向 servlet 指示 servlet 正在停止服务。
     * 只有在 servlet 的服务方法中的所有线程都退出或经过超时时间之后,才会调用此方法。 
     * servlet容器调用该方法后,不会再在该servlet上调用service方法。
     */
    public void destroy();

    /**
     * 返回一个 ServletConfig 对象,其中包含此 servlet 的初始化和启动参数。
     */
    public ServletConfig getServletConfig();

    /**
     * 返回有关 servlet 的信息,例如作者、版本和版权。
     * 此方法返回的字符串应该是纯文本,而不是任何类型的标记(例如 HTML、XML 等)
     */
    public String getServletInfo();
}

2) HttpServlet

  • 重写 service() 方法,根据 请求方法名称 是 GET | POST | DELETE 等进行分发,给具体的函数进行处理
    • service() 也可被子类重写
  • 我们要实现自定义 Servlet,就需要 继承该抽象类,重写里面的 doGet & doPost 等方法
public abstract class HttpServlet extends GenericServlet {
    public HttpServlet() {
        // NOOP
    }
    
    // 重写了 父类 的方法,将请求方法是 GET | POST | DELETE 等进行分发,给具体的函数进行处理
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod(); // 获取 请求的方法名称

        // 根据 请求的方法名称 分发给具体的函数 进行处理
        if (method.equals(METHOD_GET)) {
            // ...
            doGet(req, resp); // 执行 doGet() 方法
        } else if (method.equals(METHOD_HEAD)) {
            // ...
            doHead(req, resp);
        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);

        } else {
            // ...
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }
    
    // 提供可供子类重写的 doGet 方法
    // 常用的就是 doGet & doPost 方法
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
    {
        String msg = lStrings.getString("http.method_get_not_supported");
        sendMethodNotAllowed(req, resp, msg);
    }
    
    // doPost
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String msg = lStrings.getString("http.method_post_not_supported");
        sendMethodNotAllowed(req, resp, msg);
    }
}
上次编辑于: 2022/10/10 下午8:43:48
贡献者: liuxianzhishou