深入拆解TomcatJetty(二)
深入拆解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
。
一个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。
因此连接器可以分为 ProtocolHandler 和 Adatper 组件,其中 ProtocolHandler 包括 Endpoint 和 Processor。
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 等,这些具体实现类实现了特定协议的解析方法和请求处理方式。
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 种容器不是平行关系,而是父子关系。
Context 表示一个 Web 应用程序;Wrapper 表示一个 Servlet,一个 Web 应用程序中可能会有多个 Servlet;Host 代表的是一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可以部署多个 Web 应用程序;Engine 表示引擎,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine。
可以与Tomcat配置文件结合起来理解:
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
如图所示:
需要注意的是,请求并不是到 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 第一个 Valuepublic 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。
整个调用过程由连接器中的 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 requestApplicationFilterChain filterChain =ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);// Call the filter chain for this request// NOTE: This also calls the servlet's service() methodContainer container = this.container;try {if ((servlet != null) && (filterChain != null)) {// Swallow output if neededif (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 {// 一般走的是这个 doFilterfilterChain.doFilter(request.getRequest(), response.getResponse());}}}}
调用栈如图:
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 的一个属性
- Servlet 规范中 ServletContext 表示 web 应用的上下文环境,而 web 应用对应 tomcat 的概念是Context,所以从设计上,ServletContext 自然会成为 tomcat 的 Context 具体实现的一个成员变量。
- tomcat内部实现也是这样完成的,ServletContext 对应 tomcat 实现是org.apache.catalina.core.ApplicationContext,Context 容器对应 tomcat 实现是org.apache.catalina.core.StandardContext。ApplicationContext 是 StandardContext 的一个成员变量。
- Spring 的 ApplicationContext 之前已经介绍过,tomcat 启动过程中 ContextLoaderListener 会监听到容器初始化事件,它的contextInitialized 方法中,Spring 会初始化全局的 Spring 根容器 ApplicationContext,初始化完毕后,Spring 将其存储到ServletContext 中。
- 总而言之,Servlet 规范中 ServletContext 是 tomcat 的 Context 实现的一个成员变量,而 Spring 的 ApplicationContext 是 Servlet规范中 ServletContext 的一个属性。
相关文章:

深入拆解TomcatJetty(二)
深入拆解Tomcat&Jetty(二) 专栏地址:https://time.geekbang.org/column/intro/100027701 1、Tomcat支持的IO模型和应用层协议 IO模型: NIO:非阻塞 I/O,采用 Java NIO 类库实现。NIO2:异…...

单元化架构,分布式系统的新王!
0 关键收获 单元化架构通过减少故障的爆炸半径来增加系统弹性单元化架构是那些任何停机时间都被认为是不可接受的,或者可以显著影响最终用户的系统的一个好选择单元化架构通过强制使用固定大小的单元作为部署单元,并倾向于扩展而不是扩展的方法…...

【力扣打卡系列】滑动窗口与双指针(乘积小于K的子数组)
坚持按题型打卡&刷&梳理力扣算法题系列,语言为go,Day6 乘积小于K的子数组 题目描述解题思路 双指针移动,遍历右端点right,滑动左端点left子数组的个数:固定右端点r,子数组的个数其实就是从l到r的元…...
浅谈微前端【qiankun】的应用
一、为什么要使用微前端 微前端的核心理念是将一个大型的单体前端应用拆分成多个独立的小型应用,以便各个应用能够独立开发、部署和更新。这带来了以下几个好处: 独立开发与部署:各个团队可以独立开发自己的子应用,快速上线新功能…...

【JavaEE】——四次挥手,TCP状态转换,滑动窗口,流量控制
阿华代码,不是逆风,就是我疯 你们的点赞收藏是我前进最大的动力!! 希望本文内容能够帮助到你!! 目录 一:断开连接的本质 二:四次挥手 1:FIN 2:过程梳理 …...

D42【python 接口自动化学习】- python基础之函数
day42 高阶函数 学习日期:20241019 学习目标:函数﹣- 55 高阶函数:函数对象与函数调用的用法区别 学习笔记: 函数对象和函数调用 # 函数对象和函数调用 def foo():print(foo display)# 函数对象 a foo print(a) # &…...

GitLab 老旧版本如何升级?
极狐GitLab 正式对外推出 GitLab 专业升级服务 https://dl.gitlab.cn/cm33bsfv! 专业的技术人员为您的 GitLab 老旧版本实例进行专业升级!服务详情可以在官网查看详细解读! 那些因为老旧版本而被攻击的例子 话不多说,直接上图&a…...

现今 CSS3 最强二维布局系统 Grid 网格布局
深入学习 CSS3 目前最强大的布局系统 Grid 网格布局 Grid 网格布局的基本认识 Grid 网格布局: Grid 布局是一个基于网格的二位布局系统,是目前 CSS 最强的布局系统,它可以同时对列和行进行处理(它将网页划分成一个个网格,可以任…...

【图解版】力扣第146题:LRU缓存
力扣第146题:LRU缓存 一、LRU算法1. 基本概念2. LRU 和 LFU 的区别:3. 为什么 LRU 不需要记录使用频率? 二、Golang代码实现三、代码图解1. LRUCache、DLinkedNode两个结构体2. 初始化结构体对象3. addToHead函数4. removeNode函数5. moveToH…...

数据库知识点整理
DDL DDL-数据库操作 show databases ------------ 查看所有数据库 select database(); ----------查看当前数据库 create database 数据库名;---- 创建数据库 use 数据库名; --------------使用数据库 drop database 数据库名;--…...

【JVM】内存模型
文章目录 内存模型的基本概念案例 程序计数器栈Java虚拟机栈局部变量表栈帧中局部变量表的实际状态栈帧中存放的数据有哪些 操作数栈帧数据 本地方法栈 堆堆空间是如何进行管理的? 方法区静态变量存储 直接内存直接内存的作用 内存模型的基本概念 在前面的学习中,我们知道了字…...
代码随想录:二叉树的四种遍历
144. 二叉树的前序遍历 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullpt…...

【Linux】从多线程同步到生产者消费者模型:多线程编程实践
目录 1.线程的同步 1.1.为什么需要线程的同步? 2.2.条件变量的接口函数 2.生产消费模型 2.1 什么是生产消费模型 2.2.生产者消费者模型优点 2.3.为何要使用生产者消费者模型 3.基于BlockingQueue的生产者消费者模型 3.1为什么要将if判断变成whileÿ…...

如何在word里面给文字加拼音?
如何在word里面给文字加拼音?在现代社会,阅读已经成为了我们日常生活中不可或缺的一部分。尤其是在学习汉语的过程中,拼音的帮助显得尤为重要。为了帮助大家更好地理解和掌握汉字的发音,许多教师和学生都希望能够在Word文档中为文…...

Detr论文精读
摘要: 作者提到,该方法将物体检测看做直接的集合预测,在传统的目标检测算法中,会先生成候选区域,然后对每个候选区域进行单独的预测(包括物体的分类和预测框的回归),集合预测就是直…...

找寻孤独伤感视频素材的热门资源网站推荐
在抖音上,伤感视频总是能够引起观众的共鸣,很多朋友都在寻找可以下载伤感视频素材的地方。作为一名资深的视频剪辑师,今天我来分享几个提供高清无水印伤感素材的网站,如果你也在苦苦寻找这些素材,不妨看看以下推荐&…...

大模型~合集13
我自己的原文哦~ https://blog.51cto.com/whaosoft/12302606 #TextRCNN、TextCNN、RNN 小小搬运工周末也要学习一下~~虽然和世界没关 但还是地铁上看书吧, 大老勿怪 今天来说一下 文本分类必备经典模型 模型 SOTA!模型资源站收录情况 模型来源论文 RAE ht…...
【Next.js 项目实战系列】04-修改 Issue
原文链接 CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话,给我的库点个star,关注一下吧 上一篇【Next.js 项目实战系列】03-查看 Issue 修改 Issue 添加修改 Button 本节代码链接 安装 Radix UI 的 Ra…...

【Linux】并行与并发(含时间片)
简单来说 并发:多个进程轮流使用同一个CPU,在逻辑层面上,一段时间内推进完成了多个进程 并行:机器中有多个CPU可以使用,在物理层面上,做到同一时间会有多个进程同时在运行 举个例子:一群人需要…...
【Flutter】页面布局:弹性布局(Flex)
在 Flutter 开发中,布局是非常重要的部分。布局系统允许开发者控制和管理界面上的组件如何排列和展示。弹性布局(Flex)是其中一个非常强大且常用的布局组件,它能够在水平方向或垂直方向上灵活调整子组件的空间分配比例。Row 和 Co…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
Python爬虫实战:研究MechanicalSoup库相关技术
一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...