深入拆解TomcatJetty(二)

news2024/11/26 12:41:38

深入拆解Tomcat&Jetty(二)

专栏地址:https://time.geekbang.org/column/intro/100027701

1、Tomcat支持的IO模型和应用层协议

IO模型:

  • NIO:非阻塞 I/O,采用 Java NIO 类库实现。
  • NIO2:异步 I/O,采用 JDK 7 最新的 NIO2 类库实现。
  • APR:采用 Apache 可移植运行库实现,是 C/C++ 编写的本地库。

应用层协议:

  • HTTP/1.1:这是大部分 Web 应用采用的访问协议。
  • AJP:用于和 Web 服务器集成(如 Apache)。
  • HTTP/2:HTTP 2.0 大幅度的提升了 Web 性能。
2、总体架构

Tomcat 要实现 2 个核心功能:

  • 处理 Socket 连接,负责网络字节流与 Request 和 Response 对象的转化。
  • 加载和管理 Servlet,以及具体处理 Request 请求。

因此 Tomcat 设计了两个核心组件连接器(Connector)和容器(Container)来分别做这两件事情。连接器负责对外交流,容器负责内部处理。

由于支持的应用层协议和IO模型不通,因此Tomcat设计为一个容器可以对应多个连接器,容器和连接器都不单独对外提供服务,而是共同组合起来才能对外提供服务,组合起来的这个整体叫做 Service

image-20241019233708138

一个Tomcat实例(Server)可以对应多个Service,每个Service又可以由多个连接器和一个容器构成,连接器与容器间通过标准的 ServletRequest 对象 和 ServletResponse 对象通信。

3、连接器
3.1、整体架构

连接器对容器屏蔽了应用层协议之间的IO模型的差别,使传递给容器的都是一个标准的 ServletRequest 对象。

连接功能又可以详细分解为以下几个部分:

  • 监听网络端口。
  • 接受网络连接请求。
  • 读取请求网络字节流。
  • 根据具体应用层协议(HTTP/AJP)解析字节流,生成统一的 Tomcat Request 对象。
  • 将 Tomcat Request 对象转成标准的 ServletRequest。
  • 调用 Servlet 容器,得到 ServletResponse。
  • 将 ServletResponse 转成 Tomcat Response 对象。
  • 将 Tomcat Response 转成网络字节流。
  • 将响应字节流写回给浏览器。

通过分析连接器的详细功能列表,我们发现连接器需要完成 3 个高内聚的功能:

  • 网络通信。
  • 应用层协议解析。
  • Tomcat Request/Response 与 ServletRequest/ServletResponse 的转化。

因此 Tomcat 的设计者设计了 3 个组件来实现这 3 个功能,分别是 EndPoint、Processor 和 Adapter。EndPoint 负责提供字节流给 Processor,Processor 负责提供 Tomcat Request 对象给 Adapter,Adapter 负责提供 ServletRequest 对象给容器。

由于 I/O 模型和应用层协议可以自由组合,比如 NIO + HTTP 或者 NIO2 + AJP。Tomcat 的设计者将网络通信和应用层协议解析放在一起考虑,设计了一个叫 ProtocolHandler 的接口来封装这两种变化点。各种协议和通信模型的组合有相应的具体实现类。比如:Http11NioProtocol 和 AjpNioProtocol。

image-20241019235027769

因此连接器可以分为 ProtocolHandler 和 Adatper 组件,其中 ProtocolHandler 包括 Endpoint 和 Processor。

image-20241019235409388

3.2、ProtocolHandler

连接器用 ProtocolHandler 来处理网络连接和应用层协议:

  • EndPoint

EndPoint 是通信端点,即通信监听的接口,是具体的 Socket 接收和发送处理器,是对传输层的抽象,因此 EndPoint 是用来实现 TCP/IP 协议的。(比如EndPoint利用Socket接口将网络字节流转化为Socket数据,可以近似看为对传输层协议的间接实现。)

EndPoint 是一个接口,对应的抽象实现类是 AbstractEndpoint,而 AbstractEndpoint 的具体子类,比如在 NioEndpoint 和 Nio2Endpoint 中,有两个重要的子组件:Acceptor 和 SocketProcessor。

其中 Acceptor 用于监听 Socket 连接请求。SocketProcessor 用于处理接收到的 Socket 请求,它实现 Runnable 接口,在 Run 方法里调用协议处理组件 Processor 进行处理。为了提高处理能力,SocketProcessor 被提交到线程池来执行。而这个线程池叫作执行器(Executor)。

  • Processor

如果说 EndPoint 是用来实现 TCP/IP 协议的,那么 Processor 用来实现 HTTP 协议,Processor 接收来自 EndPoint 的 Socket,读取字节流解析成 Tomcat Request 和 Response 对象,并通过 Adapter 将其提交到容器处理,Processor 是对应用层协议的抽象。

Processor 是一个接口,定义了请求的处理等方法。它的抽象实现类 AbstractProcessor 对一些协议共有的属性进行封装,没有对方法进行实现。具体的实现有 AJPProcessor、HTTP11Processor 等,这些具体实现类实现了特定协议的解析方法和请求处理方式。

image-20241020000511389

3.3、Adapter

由于协议不同,客户端发过来的请求信息也不尽相同,Tomcat 定义了自己的 Request 类来“存放”这些请求信息。ProtocolHandler 接口负责解析请求并生成 Tomcat Request 类。但是这个 Request 对象不是标准的 ServletRequest,也就意味着,不能用 Tomcat Request 作为参数来调用容器。Tomcat 设计者的解决方案是引入 CoyoteAdapter,这是适配器模式的经典运用,连接器调用 CoyoteAdapter 的 Sevice 方法,传入的是 Tomcat Request 对象,CoyoteAdapter 负责将 Tomcat Request 转成 ServletRequest,再调用容器的 Service 方法

为什么不在 Processor 中直接转为 ServletRequest 呢?设计者认为连接器应尽量保证其独立性,不一定非要与 Servlet 容器一起工作。另外对象转换消耗的性能也并不多。(TomcatRequest -> ServletRequest)。同时如果由于容器发生更新,只需要修改 Adapter 相关代码即可,无需修改 Processor 代码。

4、多层容器

Tomcat 设计了 4 种容器,分别是 Engine、Host、Context 和 Wrapper。这 4 种容器不是平行关系,而是父子关系。

image-20241020212340675

Context 表示一个 Web 应用程序;Wrapper 表示一个 Servlet,一个 Web 应用程序中可能会有多个 Servlet;Host 代表的是一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可以部署多个 Web 应用程序;Engine 表示引擎,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine。

可以与Tomcat配置文件结合起来理解:

image-20241020212536742

Tomcat 是用组合模式来管理这些容器的。具体实现方法是,所有容器组件都实现了 Container 接口,组合模式可以使得用户对单容器对象(Wrapper)和组合容器对象(Engine、Host、Context)的使用具有一致性。

public interface Container extends Lifecycle {
    public void setName(String name);
    public Container getParent();
    public void setParent(Container container);
    public void addChild(Container child);
    public void removeChild(Container child);
    public Container findChild(String name);
}

请求是怎么被定位到具体的Servlet呢?Tomcat 设计了Mapper组件,它的工作原理是:Mapper 组件里保存了 Web 应用的配置信息,其实就是容器组件与访问路径的映射关系,比如 Host 容器里配置的域名、Context 容器里的 Web 应用路径,以及 Wrapper 容器里 Servlet 映射的路径,你可以想象这些配置信息就是一个多层次的 Map。具体流程如下:

  • 根据端口号(连接器)确定 Service 和 Engine
  • 根据域名确定 Host
  • 根据 URL 确定 Context
  • 根据 URL 确定 Wrapper

如图所示:

image-20241020213408478

需要注意的是,请求并不是到 Servlet 才被处理的,实际上这个查找路径上的父子容器都会对请求做一些处理。连接器中的 Adapter 最终会调用容器的 Service 方法来执行 Servlet,最先拿到请求的是 Engine 容器,Engine 容器对请求做一些处理后,会把请求传给自己子容器 Host 继续处理,依次类推,最后这个请求会传给 Wrapper 容器,Wrapper 会调用最终的 Servlet 来处理。

Tomcat 使用责任链模式和 Pipeline-Valve 管道来实现这个操作。

public interface Pipeline extends Contained {
  public void addValve(Valve valve);
  public Valve getBasic();	// 获取链表末端的 Value,用来调用下层容器 Pipeline 第一个 Value
  public void setBasic(Valve valve);
  public Valve getFirst();
}

Value 就是其中的一个处理点:

public interface Valve {
  public Valve getNext();
  public void setNext(Valve valve);
  public void invoke(Request request, Response response)
}

Pipeline 中有 addValve 方法。Pipeline 中维护了 Valve 链表,Valve 可以插入到 Pipeline 中,对请求做某些处理。我们还发现 Pipeline 中没有 invoke 方法,因为整个调用链的触发是 Valve 来完成的,Valve 完成自己的处理后,调用 getNext.invoke() 来触发下一个 Valve 调用。

Pipeline 中的 getBasic 方法。这个 BasicValve 处于 Valve 链表的末端,它是 Pipeline 中必不可少的一个 Valve,负责调用下层容器的 Pipeline 里的第一个 Valve。

image-20241020214229336

整个调用过程由连接器中的 Adapter 触发的,它会调用 Engine 的第一个 Valve:

// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

Wrapper 容器的最后一个 Valve 会创建一个 Filter 链,并调用 doFilter 方法,最终会调到 Servlet 的 service 方法(org.apache.catalina.core.StandardWrapperValve)。

        // Create the filter chain for this request
        ApplicationFilterChain filterChain =
                ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

        // Call the filter chain for this request
        // NOTE: This also calls the servlet's service() method
        Container container = this.container;
        try {
            if ((servlet != null) && (filterChain != null)) {
                // Swallow output if needed
                if (context.getSwallowOutput()) {
                    try {
                        SystemLogHandler.startCapture();
                        if (request.isAsyncDispatching()) {
                            request.getAsyncContextInternal().doInternalDispatch();
                        } else {
                            filterChain.doFilter(request.getRequest(),
                                    response.getResponse());
                        }
                    } finally {
                        String log = SystemLogHandler.stopCapture();
                        if (log != null && log.length() > 0) {
                            context.getLogger().info(log);
                        }
                    }
                } else {
                    if (request.isAsyncDispatching()) {
                        request.getAsyncContextInternal().doInternalDispatch();
                    } else {
                        // 一般走的是这个 doFilter
                        filterChain.doFilter
                            (request.getRequest(), response.getResponse());
                    }
                }

            }
        }

调用栈如图:

image-20241020220803644

Value 和 Web 应用里的 Filter 有什么区别

  • Value 是 Tomcat 的私有机制,与 Tomcat 基础架构/API 是紧耦合的。Filter 是 Servlet API 公有的标准,所有的 Web 容器都支持。
  • Value 工作在容器级别,可以拦截到所有的请求;而 Filter 工作在应用级别,只能拦截某个 Web 应用的所有请求。如果一个 Tomcat 部署了多个应用,只能通过 Value (Host 或 Engine级别)实现统一拦截。

Tomcat 内的 Context 组件跟 Servlet 规范中的 ServletContext 接口有什么区别?跟 Spring 中的 ApplicationContext 又有什么关系?

  • Tomcat 的 Context 是一个 Web 应用; Servlet 的 ServletContext 是 Web 应用上下文, 是 Context 的一个成员变量;
  • Spring 的 ApplicationContext 是 spring 容器, 是 ServletContext 的一个属性
  1. Servlet 规范中 ServletContext 表示 web 应用的上下文环境,而 web 应用对应 tomcat 的概念是Context,所以从设计上,ServletContext 自然会成为 tomcat 的 Context 具体实现的一个成员变量。
  2. tomcat内部实现也是这样完成的,ServletContext 对应 tomcat 实现是org.apache.catalina.core.ApplicationContext,Context 容器对应 tomcat 实现是org.apache.catalina.core.StandardContext。ApplicationContext 是 StandardContext 的一个成员变量。
  3. Spring 的 ApplicationContext 之前已经介绍过,tomcat 启动过程中 ContextLoaderListener 会监听到容器初始化事件,它的contextInitialized 方法中,Spring 会初始化全局的 Spring 根容器 ApplicationContext,初始化完毕后,Spring 将其存储到ServletContext 中。
  4. 总而言之,Servlet 规范中 ServletContext 是 tomcat 的 Context 实现的一个成员变量,而 Spring 的 ApplicationContext 是 Servlet规范中 ServletContext 的一个属性。

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

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

相关文章

Cyber RT 之 Timer Component 实践(apollo 9.0)

实验内容 Component 是 Cyber RT 提供的用来构建功能模块的基础类,Component 有两种类型,分别为 Component 和 TimerComponent。 相较于 Component,TimerComponent 不提供消息融合,也不由消息触发运行,而是由系统定时…

UE5 gameplay学习 蓝图0 level blueprint

首先在左上角这个位置可以创建一个这个蓝图 我理解这个蓝图适合做全局事件规划啥的 在场景选中一个物体,右侧面板拿到他,直接拖入蓝图,就能操作他了 这里获取到了这个物体,在gamebegin的时候把Z加了500 执行播放的时候能看见他从…

Windows API 一 ----起步

目录 1.介绍主函数入口参数。 2. 简单介绍 Windows.h 这个头文件 小结,也聊一聊 1.介绍主函数入口参数。 第一个参数: HINSTANCE 类型的 参数, 称为“实例句柄“,这个参数唯一标志了我们写的这个程序。 第二个参数: HINSTANCE…

poisson过程——随机模拟(Python和R实现)

Python实现 exponential()使用,自动poisson过程实现。 import numpy as np import matplotlib.pyplot as plt# Parameters lambda_rate 5 # rate parameter (events per time unit) T 10 # total time# Generate Poisson process times np.random.exponential(…

k8s系列-Rancher 上操作的k8s容器网络配置总结

Rancher 上操作的k8s容器网络配置总结 要在 Rancher 中配置Spring Boot 应用 ykhd-zhjgyw-xpwfxfjfl 服务,正确的配置方式如下: 1. 应用程序监听端口 在 application.yaml 文件中,配置的应用监听端口是 10001,并且应用的上下文…

Mycat 详细介绍及入门实战,解决数据库性能问题

一、基本原理 1、数据分片 (1)、水平分片 Mycat 将一个大表的数据按照一定的规则拆分成多个小表,分布在不同的数据库节点上。例如,可以根据某个字段的值进行哈希取模,将数据均匀的分布到不同的节点上。 这样做的好处…

美摄科技云服务解决方案,方案成熟,接入简单

美摄科技作为视频处理领域的先锋,凭借其强大的技术实力和深厚的行业经验,推出了成熟的云服务解决方案,为轻量化视频制作开辟了全新的道路。 一、成熟方案,接入无忧 美摄科技云服务解决方案的最大亮点在于其成熟度和易用性。我们…

RabbitMQ 入门(四)SpringAMQP五种消息类型(Work Queue)

一、WorkQueue(工作消息队列) Work queues,也被称为(Task queues),任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息。 当消息处理比较耗时的时候,可能生产消息的速度会远远大于…

react里实现左右拉伸实战

封装组件: 新建一个resizeBox.tsx文件写上代码如下: import React, { ReactNode, useState, useEffect, useRef } from react; import styles from "./resizeBox.less"; interface ResizableBoxProps {/*** 盒子的宽度*/widthNum?: number;…

比较相同机器上 redis和mysql分别单独承载的 最大连接数量

在相同的机器上,Redis 和 MySQL 的最大连接数量会受到硬件配置(如 CPU、内存、网络等)、配置参数和应用场景的影响。以下是对 Redis 和 MySQL 在单机环境下最大连接数的比较: Redis 最大连接数量 默认配置: Redis 默…

【2024最新版】网络安全学习路线-适合入门小白

首先说明,我是一名CTF的web手,这是我自己亲身学习网络安全的路线,希望能够帮到大家,我虽然不是大牛,但我也希望能够帮助一些网安小白找到自己学习的方向,后面有就业的详细安全技术要求,如果真想…

yolov8实例分隔

1.查看显卡型号 2.在https://en.wikipedia.org/wiki/CUDA上查看显卡算力,这里显卡为1650,算力为7.5 3.查看显卡算力对应的cuda版本 4slurm上该怎么办? 查看slurm上计算节点cuda版本 查看cuda版本 srun -A 2022099 -J job1 -p Gnode --…

【Echarts 实战指南】解锁动态历史曲线之谜

在工作中,大家是否曾遇到过这样一种需求呢?需获取设备最近 10 分钟的历史数据。设备实时数据每 2 秒推送一次,且要把历史数据曲线变成动态变化的状态。倘若设备最近 10 分钟的历史数据为 20 个点,那么现在每 2 秒就要将最前面的点…

Java爬虫:获取直播带货数据的实战指南

在当今数字化时代,直播带货已成为电商领域的新热点,通过直播平台展示商品并进行销售,有效促进了产品的曝光和销售量的提升。然而,如何在直播带货过程中进行数据分析和评估效果,成为了摆在商家面前的一个重要问题。本文…

工业相机有哪些应用场景

工业相机具有高性能、高稳定性和高可靠性的特点,因此在众多工业领域都有广泛的应用场景。以下是朗观视觉小编总结的一些典型的应用场景: 机器视觉与自动化: 工业相机在机器视觉系统中起着核心作用,用于捕捉和分析物体的图像&#…

【Linux】从多线程同步到生产者消费者模型:多线程编程实践

目录 1.线程的同步 1.1.为什么需要线程的同步? 2.2.条件变量的接口函数 2.生产消费模型 2.1 什么是生产消费模型 2.2.生产者消费者模型优点 2.3.为何要使用生产者消费者模型 3.基于BlockingQueue的生产者消费者模型 3.1为什么要将if判断变成while&#xff…

API的力量:解决编程技术问题的利器

在软件开发的世界里,编程技术问题无处不在。从数据获取到用户认证,从支付处理到地图服务,这些问题的解决方案往往需要深厚的专业知识和大量的开发时间。然而,应用程序编程接口(API)的出现,为开发…

架构师备考-背诵精华(系统架构设计)

软件架构风格 类型 子类型 说明 数据流风格 批处理 每个处理步骤是一个单独的程序,每一步必须在前一步结束后才能开始,而且数据必须是完整的,以整体的方式传递。 前面的构件处理完,后面构件才能处理;数据完整传输…

(五)若使用LQR控制小车倒立摆,该如何对小车和摆杆的动力学方程线性化?哪些变量是可以进行简化的,线性化后的状态空间方程应该怎么列写

写在前面: 关于lqr控制的讲解,可以观看如下三个视频: 2. LQR数学公式理解_哔哩哔哩_bilibili 如何感性地理解LQR控制?_哔哩哔哩_bilibili LQR简介与使用_哔哩哔哩_bilibili 正文: 在之前系列的文章中我们已经得出…

scala 抽象类

理解抽象类 抽象的定义 定义一个抽象类 :abstract class A {} idea实例 抽象类重写 idea实例 练习 1.abstract2.错3.abstract class A{}4.对