总体架构

Tomcat 是一个应用服务器,那么要开发一个应用服务器,首先捋一捋它的需求,要实现那些功能。

1、 首先可以和客户端建立连接,并且能够处理客户端的连接

2、 其次还要解析处理我们写的 Servlet

3、 最后能够根据客户端的请求找到相应的 Servlet。

在 Tomcat 中将这些需求分为了两大功能

  • 处理 Socket 连接,并将网络字节流转换成 Request 对象和 Response 对象
  • 解析、加载和管理 Servlet,处理请求并返回响应数据

Tomcat 将这两大功能,设计成了两个主要的组件

  • 连接器(Connector)
  • 容器(Container)

来看一下 Tomcat 的总体架构

all

上图中是 Tomcat 的整体架构,一个 Tomcat 代表一个 Server,一个 Server 下包含对个 Service,每个 Service 下包含多个连接器和一个容器。

Service 本身没有什么重要的作用,它只是把连接器和容器组装在一起了,但是 Tomcat 可以同时设置多个 Service,也就可以部署多个服务。比如有两个相同的项目,可以把这两个项目放到两个 Service 里,那这两个相同的项目就可以在一个 Tomcat 里运行了,不用担心冲突的问题。

这些配置可以在 conf/server.xml 中查看。

接下来重点关注一下连接器和容器,这是 Tomcat 工作的核心。

连接器(Connector)

在分析连接器之前,先了解一下 Tomcat 支持的 I/O 模型和应用层协议。

I/O 模型:

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

应用层协议:

  • HTTP/1.1
  • AJP:用于和 Web 服务器集成(如 Apache)。
  • HTTP/2:HTTP 2.0 大幅度的提升了 Web 性能。

Service 中存在多个连接器就是为了支持 Tomcat 的多个 I/O 模型和应用层协议。

OK,现在来分析连接器。

首先可以先看一看 Tomcat 中连接器的配置

1
2
3
4
5
6
<Connector port="24100" protocol="HTTP/1.1"
connectionTimeout="20000"
compression="on"
compressionMinSize="2048"
noCompressionUserAgents="gozilla, traviata"
compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain,image/jpeg,image/gif" />

连接器中配置了监听的端口和使用的应用层协议等信息。

在上面说了,连接器的主要作用是处理 Socket 连接,并将网络字节流转换成 Request 对象和 Response 对象。那么我可以再试着捋一捋连接器的需求

1、 监听端口

2、 建立连接

3、 获取客户端传输的字节流

4、 根据应用层协议解析字节流,将解析的数据交给容器处理

5、 容器返回响应

6、 将响应转换成字节流返回给客户端

根据以上的需求,Tomcat 将整个连接器分为了三部分

  • 网络通信
  • 解析应用层协议
  • 与容器进行交互

Tomcat 将这三个功能分成了三个模块:Endpoint、Processor 和 Adapter,三个模块只通过抽象接口进行交互,封装了变化,降低了耦合度。

三个模块的处理逻辑为:

1、Endpoint 接收字节流,并交给 Processor。

2、Processor 拿到字节流后,将字节流解析成 Tomcat Request 对象并交给 Adapter。

3、Adapter 拿到 Tomcat Request 对象再解析成 ServletRequest 交给容器。

Tomcat 并没有直接将字节流解析成 ServletRequest 而是先解析成了 Tomcat Request,再通过 Adapter 进行转换,这样做的好处可以使连接器和容器接偶,我们可以自己实现 Adapter 的功能,来对接我们自己实现的类似容器的功能。

由于 Tocmat 支持多种 I/O 模型和应用层协议,并且这些 I/O 模型和应用层协议可以自由组合,比如 NIO + HTTP 或者 NIO2 + AJP。Tomcat 设计了一个 ProtocolHandler,将网络通信和解析应用层协议放到了一起,来封装这两点的变化。

来看一下连接器的结构图

connector

来看一下连接器各个组件的代码结构

Endpoint

Endpoint 不是一个接口,只有对应的实现类 AbstractEndpoint

Endpoint

AbstractEndpoint 的实现类中包含了 Tomcat 支持的 I/O 模型。

Processor

Processor

Processor 的实现类是包含了 Tomcat 支持的所有应用协议。

ProtocolHandler

ProtocolHandler

ProtocolHandler 的实现类里包含了每一种 I/O 模型和协议的组合。

Adapter

Adapter 只有一个实现类 CoyoteAdapter,CoyoteAdapter 是一个典型的适配器模式的使用,ProtocolHandler 中将不同的 IO 模式和不同的应用层协议通过 Endpoint 和 Processor 封装成 Tomcat Request,这个 Request 在 Adapter 中转换成标准的 ServletRequest。这其实也是一个扩展点,我们可以实现自己的 Adapter,拿到 Request 进行自己的业务处理,甚至可以不用 Servlet 那一套,自己定义一套新的应用处理模式。

容器(Container)

容器的作用是解析、加载和管理 Servlet,处理请求并返回响应数据。在 Tomcat 中设计了四种容器 Engine、Host、Context 和 Wrapper,这四种容器是父子关系。

  • Engine 表示引擎,用来管理多个虚拟站点

  • Host 代表的是一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址

  • Context 表示一个 Web 应用程序,也是就我们写的一个项目

  • Wrapper 表示一个 Servlet

一个 Service 最多只能有一个 Engine,一个 Engine 中可以包含多个 Host,一个 Host 中可以包含多个个 Context,一个 Context 可以包含多个 Wrapper

看一下它的结构图

Container

可以结合 conf/server.xml 配置文件来理解容器的设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">

<!--连接器-->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />

<!--容器-->
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
</Host>
</Engine>
</Service>
</Server>

Tomcat 容器是怎么确定请求的是那个 Servlet 的呢?

通过一个例子来说明一下,图片是盗来的,哈哈哈哈。

demo

上面这个例子中,要访问 http://user.shopping.com:8080/order/buy。 Tomcat通过连接器解析数据后,交给容器,

1、 根据域名找到对应的 Host,也就是在 conf/server.xml 中配置的和 Host 的 name 相同的 Host

2、根据 URL 找到 Context

3、根据 URL 找到 Wrapper(Servlet)

当连接器将数据给到容器后,并不是直到找到 Servlet 才开始处理数据,容器的每一层都会对数据进行一些处理。Tomcat 用了一个叫做 Pipeline-Valve 管道的方式来对数据进行处理。

Pipeline-Valve 管道

Pipeline-Valve 管道是一种责任链模式,其中 Valve 表示一个处理点,Pipeline 中包含多个 Valve,每个容器中包含一个 Pipeline,每个容器中的 Pipeline 必须包含一个 BasicValve,处于调用的最末端,负责调用下个容器的 Value。

用一张图来解释一下

Pipeline-Valve

Wrapper 容器的最后一个 Valve 会创建一个 Filter 链,并调用 doFilter 方法,最终会调到 Servlet 的 service 方法。

来看一下容器的代码结构

Tomcat 设计了一个顶层的容器接口

1
2
3
4
5
6
7
8
9
10
public interface Container extends Lifecycle {

public Container getParent();

public void setParent(Container container);

public void addChild(Container child);

// ....省略
}

各个容器继承了这个顶层的容器

ContainerClass

Container中定义了操作父容器和子容器的方法,很明显的组合模式。

再来看看每个实现类的结构

standContainerClass

每个类同时又继承了一个 Container 的实现抽象类ContainerBase,看一下这个类

1
2
3
4
5
6
7
public abstract class ContainerBase extends LifecycleMBeanBase
implements Container {

protected final Pipeline pipeline = new StandardPipeline(this);

// ....省略
}

在 ContainerBase 中有Pipeline的属性,这就是 Pipeline-Valve 管道。

OK,最后结合Java类来看看Tomcat组件的总体结构。

class