Skip to content

Commit

Permalink
feat: 整理图片
Browse files Browse the repository at this point in the history
  • Loading branch information
dunwu committed Jan 27, 2024
1 parent 8934acd commit d2f55e8
Show file tree
Hide file tree
Showing 31 changed files with 137 additions and 137 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<p align="center">
<a href="https://dunwu.github.io/java-tutorial/" target="_blank" rel="noopener noreferrer">
<img src="https://raw.githubusercontent.com/dunwu/images/dev/common/dunwu-logo.png" alt="logo" width="150px"/>
<img src="https://raw.githubusercontent.com/dunwu/images/master/common/dunwu-logo.png" alt="logo" width="150px"/>
</a>
</p>

Expand Down
2 changes: 1 addition & 1 deletion docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ module.exports = {
}
],
sidebarDepth: 2, // 侧边栏显示深度,默认1,最大2(显示到h3标题)
logo: 'https://raw.githubusercontent.com/dunwu/images/dev/common/dunwu-logo.png', // 导航栏logo
logo: 'https://raw.githubusercontent.com/dunwu/images/master/common/dunwu-logo.png', // 导航栏logo
repo: 'dunwu/java-tutorial', // 导航栏右侧生成Github链接
searchMaxSuggestions: 10, // 搜索结果显示最大数
lastUpdated: '上次更新', // 更新的时间,及前缀文字 string | boolean (取值为git提交时间)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ permalink: /pages/5ecb29/

Filter 提供了过滤链(Filter Chain)的概念,一个过滤链包括多个 Filter。客户端请求 request 在抵达 Servlet 之前会经过过滤链的所有 Filter,服务器响应 response 从 Servlet 抵达客户端浏览器之前也会经过过滤链的所有 FIlter。

![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/1559054413341.png)
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/1559054413341.png)

### 过滤器方法

Expand Down
42 changes: 21 additions & 21 deletions docs/02.JavaEE/02.服务器/01.Tomcat/01.Tomcat快速入门.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ tar -zxf apache-tomcat-8.5.24.tar.gz

启动后,访问 `http://localhost:8080` ,可以看到 Tomcat 安装成功的测试页面。

![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/tools/tomcat/tomcat.png)
![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/tools/tomcat/tomcat.png)

### 2.2. 配置

Expand Down Expand Up @@ -364,7 +364,7 @@ public class SimpleTomcatServer {
- 设置启动应用的端口、JVM 参数、启动浏览器等。
- 成功后,可以访问 `http://localhost:8080/`(当然,你也可以在 url 中设置上下文名称)。

![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/tools/tomcat/tomcat-intellij-run-config.png)
![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/tools/tomcat/tomcat-intellij-run-config.png)

> **说明**
>
Expand All @@ -374,7 +374,7 @@ public class SimpleTomcatServer {

## 3. Tomcat 架构

![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201113193431.png)
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201113193431.png)

Tomcat 要实现 2 个核心功能:

Expand Down Expand Up @@ -402,7 +402,7 @@ Tomcat 支持的应用层协议有:

Tomcat 支持多种 I/O 模型和应用层协议。为了实现这点,一个容器可能对接多个连接器。但是,单独的连接器或容器都不能对外提供服务,需要把它们组装起来才能工作,组装后这个整体叫作 Service 组件。Tomcat 内可能有多个 Service,通过在 Tomcat 中配置多个 Service,可以实现通过不同的端口号来访问同一台机器上部署的不同应用。

![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201111093124.png)
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201111093124.png)

**一个 Tomcat 实例有一个或多个 Service;一个 Service 有多个 Connector 和 Container**。Connector 和 Container 之间通过标准的 ServletRequest 和 ServletResponse 通信。

Expand All @@ -418,13 +418,13 @@ Tomcat 支持多种 I/O 模型和应用层协议。为了实现这点,一个

Tomcat 设计了 3 个组件来实现这 3 个功能,分别是 **`EndPoint`****`Processor`****`Adapter`**

![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201111101440.png)
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201111101440.png)

组件间通过抽象接口交互。这样做还有一个好处是**封装变化。**这是面向对象设计的精髓,将系统中经常变化的部分和稳定的部分隔离,有助于增加复用性,并降低系统耦合度。网络通信的 I/O 模型是变化的,可能是非阻塞 I/O、异步 I/O 或者 APR。应用层协议也是变化的,可能是 HTTP、HTTPS、AJP。浏览器端发送的请求信息也是变化的。但是整体的处理逻辑是不变的,EndPoint 负责提供字节流给 Processor,Processor 负责提供 Tomcat Request 对象给 Adapter,Adapter 负责提供 ServletRequest 对象给容器。

如果要支持新的 I/O 方案、新的应用层协议,只需要实现相关的具体子类,上层通用的处理逻辑是不变的。由于 I/O 模型和应用层协议可以自由组合,比如 NIO + HTTP 或者 NIO2 + AJP。Tomcat 的设计者将网络通信和应用层协议解析放在一起考虑,设计了一个叫 ProtocolHandler 的接口来封装这两种变化点。各种协议和通信模型的组合有相应的具体实现类。比如:Http11NioProtocol 和 AjpNioProtocol。

![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201027091819.png)
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201027091819.png)

#### 3.2.1. ProtocolHandler 组件

Expand All @@ -444,7 +444,7 @@ EndPoint 是一个接口,对应的抽象实现类是 AbstractEndpoint,而 Ab

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

![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201113185929.png)
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201113185929.png)

从图中我们看到,EndPoint 接收到 Socket 连接后,生成一个 SocketProcessor 任务提交到线程池去处理,SocketProcessor 的 Run 方法会调用 Processor 组件去解析应用层协议,Processor 通过解析生成 Request 对象后,会调用 Adapter 的 Service 方法。

Expand All @@ -471,7 +471,7 @@ Tomcat 是怎么确定请求是由哪个 Wrapper 容器里的 Servlet 来处理

举例来说,假如有一个网购系统,有面向网站管理人员的后台管理系统,还有面向终端客户的在线购物系统。这两个系统跑在同一个 Tomcat 上,为了隔离它们的访问域名,配置了两个虚拟域名:`manage.shopping.com``user.shopping.com`,网站管理人员通过`manage.shopping.com`域名访问 Tomcat 去管理用户和商品,而用户管理和商品管理是两个单独的 Web 应用。终端客户通过`user.shopping.com`域名去搜索商品和下订单,搜索功能和订单管理也是两个独立的 Web 应用。如下所示,演示了 url 应声 Servlet 的处理流程。

![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201113192022.jpg)
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201113192022.jpg)

假如有用户访问一个 URL,比如图中的`http://user.shopping.com:8080/order/buy`,Tomcat 如何将这个 URL 定位到一个 Servlet 呢?

Expand All @@ -490,7 +490,7 @@ Pipeline-Valve 是责任链模式,责任链模式是指在一个请求处理

先来了解一下 Valve 和 Pipeline 接口的设计:

![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/tools/tomcat/Pipeline与Valve.png)
![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/tools/tomcat/Pipeline与Valve.png)

- 每一个容器都有一个 Pipeline 对象,只要触发这个 Pipeline 的第一个 Valve,这个容器里 Pipeline 中的 Valve 就都会被调用到。但是,不同容器的 Pipeline 是怎么链式触发的呢,比如 Engine 中 Pipeline 需要调用下层容器 Host 中的 Pipeline。
- 这是因为 Pipeline 中还有个 getBasic 方法。这个 BasicValve 处于 Valve 链表的末端,它是 Pipeline 中必不可少的一个 Valve,负责调用下层容器的 Pipeline 里的第一个 Valve。
Expand All @@ -499,7 +499,7 @@ Pipeline-Valve 是责任链模式,责任链模式是指在一个请求处理
- 各层容器对应的 basic valve 分别是 `StandardEngineValve``StandardHostValve``StandardContextValve``StandardWrapperValve`
- 由于 Valve 是一个处理点,因此 invoke 方法就是来处理请求的。注意到 Valve 中有 getNext 和 setNext 方法,因此我们大概可以猜到有一个链表将 Valve 链起来了。

![img](https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/tools/tomcat/请求处理过程.png)
![img](https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/tools/tomcat/请求处理过程.png)

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

Expand All @@ -511,7 +511,7 @@ connector.getService().getContainer().getPipeline().getFirst().invoke(request, r

### 4.1. Tomcat 的启动过程

![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201118145455.png)
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201118145455.png)

1. Tomcat 是一个 Java 程序,它的运行从执行 `startup.sh` 脚本开始。`startup.sh` 会启动一个 JVM 来运行 Tomcat 的启动类 `Bootstrap`
2. `Bootstrap` 会初始化 Tomcat 的类加载器并实例化 `Catalina`
Expand Down Expand Up @@ -731,12 +731,12 @@ ContextConfig 解析 web.xml 顺序:

### 4.3. LifeCycle

![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201118105012.png)
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201118105012.png)

#### 4.3.1. 请求处理过程

<div align="center">
<img src="https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/tools/tomcat/请求处理过程.png" width="600">
<img src="https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/tools/tomcat/请求处理过程.png" width="600">
</div>

1. 根据 server.xml 配置的指定的 connector 以及端口监听 http、或者 ajp 请求
Expand All @@ -747,25 +747,25 @@ ContextConfig 解析 web.xml 顺序:
### 4.4. Connector 流程

<div align="center">
<img src="https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/tools/tomcat/connector.png" width="600">
<img src="https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/tools/tomcat/connector.png" width="600">
</div>

#### 4.4.1. 阻塞 IO

<div align="center">
<img src="https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/tools/tomcat/阻塞IO.png" width="600">
<img src="https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/tools/tomcat/阻塞IO.png" width="600">
</div>

#### 4.4.2. 非阻塞 IO

<div align="center">
<img src="https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/tools/tomcat/非阻塞IO.png" width="600">
<img src="https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/tools/tomcat/非阻塞IO.png" width="600">
</div>

#### 4.4.3. IO 多路复用

<div align="center">
<img src="https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/tools/tomcat/IO多路复用.png" width="600">
<img src="https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/tools/tomcat/IO多路复用.png" width="600">
</div>

阻塞与非阻塞的区别在于进行读操作和写操作的系统调用时,如果此时内核态没有数据可读或者没有缓冲空间可写时,是否阻塞。
Expand All @@ -775,7 +775,7 @@ IO 多路复用的好处在于可同时监听多个 socket 的可读和可写事
#### 4.4.4. Tomcat 各类 Connector 对比

<div align="center">
<img src="https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/tools/tomcat/Tomcat各类Connector对比.jpg" width="500">
<img src="https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/tools/tomcat/Tomcat各类Connector对比.jpg" width="500">
</div>

- JIO:用 java.io 编写的 TCP 模块,阻塞 IO
Expand All @@ -796,7 +796,7 @@ Apache Portable Runtime 是一个高度可移植的库,它是 Apache HTTP Serv
**NIO 处理相关类**

<div align="center">
<img src="https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/tools/tomcat/NIO处理相关类.jpg" width="500">
<img src="https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/tools/tomcat/NIO处理相关类.jpg" width="500">
</div>

Poller 线程从 EventQueue 获取 PollerEvent,并执行 PollerEvent 的 run 方法,调用 Selector 的 select 方法,如果有可读的 Socket 则创建 Http11NioProcessor,放入到线程池中执行;
Expand Down Expand Up @@ -829,7 +829,7 @@ Note:
### 4.6. 异步 Servlet

<div align="center">
<img src="https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/tools/tomcat/传统Servlet处理流程.png" >
<img src="https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/tools/tomcat/传统Servlet处理流程.png" >
</div>

传统流程:
Expand All @@ -839,7 +839,7 @@ Note:
- 最后,根据处理的结果提交响应,Servlet 线程结束

<div align="center">
<img src="https://raw.githubusercontent.com/dunwu/images/dev/cs/java/javaweb/tools/tomcat/异步Servlet处理流程.png" >
<img src="https://raw.githubusercontent.com/dunwu/images/master/cs/java/javaweb/tools/tomcat/异步Servlet处理流程.png" >
</div>

异步处理流程:
Expand Down
14 changes: 7 additions & 7 deletions docs/02.JavaEE/02.服务器/01.Tomcat/02.Tomcat连接器.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ permalink: /pages/3c954b/

Tomcat 的 NioEndPoint 组件利用 Java NIO 实现了 I/O 多路复用模型。

![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201127094302.jpg)
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201127094302.jpg)

NioEndPoint 子组件功能简介:

Expand Down Expand Up @@ -123,7 +123,7 @@ private final SynchronizedQueue<PollerEvent> events = new SynchronizedQueue<>();

Nio2Endpoint 工作流程跟 NioEndpoint 较为相似。

![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201127143839.jpg)
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201127143839.jpg)

Nio2Endpoint 子组件功能说明:

Expand Down Expand Up @@ -218,7 +218,7 @@ Tomcat 本身是 Java 编写的,为了调用 C 语言编写的 APR,需要通

### 3.1. AprEndpoint 工作流程

![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201127145740.jpg)
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201127145740.jpg)

#### 3.1.1. Acceptor

Expand Down Expand Up @@ -282,7 +282,7 @@ java my.class

这个命令行中的`java`其实是**一个可执行程序,这个程序会创建 JVM 来加载和运行你的 Java**。操作系统会创建一个进程来执行这个`java`可执行程序,而每个进程都有自己的虚拟地址空间,JVM 用到的内存(包括堆、栈和方法区)就是从进程的虚拟地址空间上分配的。请你注意的是,JVM 内存只是进程空间的一部分,除此之外进程空间内还有代码段、数据段、内存映射区、内核空间等。从 JVM 的角度看,JVM 内存之外的部分叫作本地内存,C 程序代码在运行过程中用到的内存就是本地内存中分配的。下面我们通过一张图来理解一下。

![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201127150729.jpg)
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201127150729.jpg)

TomcatEndpoint 组件在接收网络数据时需要预先分配好一块 Buffer,所谓的 Buffer 就是字节数组`byte[]`,Java 通过 JNI 调用把这块 Buffer 的地址传给 C 代码,C 代码通过操作系统 API 读取 Socket 并把数据填充到这块 BufferJava NIO API 提供了两种 Buffer 来接收数据:HeapByteBufferDirectByteBuffer,下面的代码演示了如何创建两种 Buffer

Expand Down Expand Up @@ -323,7 +323,7 @@ Tomcat 中的 AprEndpoint 就是通过 DirectByteBuffer 来接收数据的,而

从下面的图你会发现这个过程有 6 次内存拷贝,并且 read 和 write 等系统调用将导致进程从用户态到内核态的切换,会耗费大量的 CPU 和内存资源。

![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201127151041.jpg)
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201127151041.jpg)

TomcatAprEndpoint 通过操作系统层面的 sendfile 特性解决了这个问题,sendfile 系统调用方式非常简洁。

Expand All @@ -337,7 +337,7 @@ sendfile(socket, file, len);

第二步:数据并没有从内核缓冲区复制到 Socket 关联的缓冲区,只有记录数据位置和长度的描述符被添加到 Socket 缓冲区中;接着把数据直接从内核缓冲区传递给网卡。这个过程你可以看下面的图。

![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201127151155.jpg)
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201127151155.jpg)

## 4. Executor 组件

Expand Down Expand Up @@ -508,7 +508,7 @@ Tomcat 用 ProtocolHandler 组件屏蔽应用层协议的差异,其中 Protoco

WebSocket 是通过 HTTP 协议来进行握手的,因此当 WebSocket 的握手请求到来时,HttpProtocolHandler 首先接收到这个请求,在处理这个 HTTP 请求时,Tomcat 通过一个特殊的 Filter 判断该当前 HTTP 请求是否是一个 WebSocket Upgrade 请求(即包含`Upgrade: websocket`的 HTTP 头信息),如果是,则在 HTTP 响应里添加 WebSocket 相关的响应头信息,并进行协议升级。具体来说就是用 UpgradeProtocolHandler 替换当前的 HttpProtocolHandler,相应的,把当前 SocketProcessor 替换成 UpgradeProcessor,同时 Tomcat 会创建 WebSocket Session 实例和 Endpoint 实例,并跟当前的 WebSocket 连接一一对应起来。这个 WebSocket 连接不会立即关闭,并且在请求处理中,不再使用原有的 HttpProcessor,而是用专门的 UpgradeProcessorUpgradeProcessor 最终会调用相应的 Endpoint 实例来处理请求。

![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201127153521.jpg)
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201127153521.jpg)

你可以看到,Tomcat 对 WebSocket 请求的处理没有经过 Servlet 容器,而是通过 UpgradeProcessor 组件直接把请求发到 ServerEndpoint 实例,并且 Tomcat 的 WebSocket 实现不需要关注具体 I/O 模型的细节,从而实现了与具体 I/O 方式的解耦。

Expand Down
2 changes: 1 addition & 1 deletion docs/02.JavaEE/02.服务器/01.Tomcat/03.Tomcat容器.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ Tomcat 作为 Web 容器,需要解决以下问题:
2. 两个 Web 应用都依赖同一个第三方的 JAR 包,比如 Spring,那 SpringJAR 包被加载到内存后,Tomcat 要保证这两个 Web 应用能够共享,也就是说 SpringJAR 包只被加载一次,否则随着依赖的第三方 JAR 包增多,JVM 的内存会膨胀。
3. 需要隔离 Tomcat 本身的类和 Web 应用的类。

![img](https://raw.githubusercontent.com/dunwu/images/dev/snap/20201130141536.png)
![img](https://raw.githubusercontent.com/dunwu/images/master/snap/20201130141536.png)

#### WebAppClassLoader

Expand Down
Loading

0 comments on commit d2f55e8

Please sign in to comment.