Tomcat架构解析

​ 参考书籍《Tomcat架构解析》

一、Tomcat基础

1. 静态/动态资源

​ 浏览器只能解析静态资源(html,css,js,jpg),而动态资源(例如Servlet,jsp等)需要转换成静态资源再返回给浏览器。

2. Web服务器软件

​ Tomcat作为一款Web服务器软件,需要兼备 服务器 和 服务器软件 的功能

具有:接收用户的请求,处理请求,做出响应,同时可以部署Web应用,让用户通过浏览器来访问这些项目。但Tomcat作为一个中小型的JavaEE服务器,仅仅支持少量的JavaEE规范servlet/jsp。

3. Tomcat主要目录结构

在这里插入图片描述

  • bin目录,存放Tomcat启动、停止的脚本

    startup.bat ,startup.sh:用于在windows和linux下的启动脚本
    shutdown.bat ,shutdown.sh:用于在windows和linux下的停止脚本

    还有Tomcat的startup启动时,实际调用的Catalina脚本

  • conf目录,用于存放Tomcat的相关配置文件

    Catalina:用于存储针对每个虚拟机的Context配置
    context.xml:用于定义所有web应用均需加载的Context配置,如果web应用指定了自己的context.xml,该文件将被覆盖
    catalina.properties:Tomcat 的环境变量配置
    catalina.policy:Tomcat 运行的安全策略配置
    logging.properties:Tomcat 的日志配置文件, 可以通过该文件修改Tomcat 的日志级别及日志路径等
    server.xml:Tomcat 服务器的核心配置文件
    tomcat-users.xml:定义Tomcat默认的用户及角色映射信息配置
    web.xml:Tomcat 中所有应用默认的部署描述文件,主要定义了基础Servlet和MIME映射。

注意:Tomcat就是根据conf路径下的server.xml的配置文件信息,来创建的服务器实例。

  • lib:Tomcat 服务器的依赖包
  • logs:Tomcat 默认的日志存放目录
  • webapps:Tomcat 默认的Web应用部署目录
  • work:Web 应用JSP代码生成和编译的临时目录

4. Http工作原理

​ HTTP协议是浏览器与服务器之间的数据传送协议。作为应用层协议,HTTP是基于TCP/IP协议来传递数据的(HTML文件、图片、查询结果等),HTTP协议不涉及数据包(Packet)传输,只是一种规定的通信格式。

在这里插入图片描述

​ 浏览器向服务端发出TCP连接请求,服务程序接受浏览器的连接请求,并经过TCP三次握手建立连接。浏览器将请求数据打包成一个HTTP协议格式的数据包,浏览器将该数据包推入网络,数据包经过网络传输,最终达到端服务程序。服务端到客户端亦然,浏览器将HTML文件展示在页面上,则Tomcat的功能步骤就分析出来了。

Tomcat作为一个Web应用服务器,主要是接受连接、解析请求数据、处理请求、发出响应这四个步骤。

二、Tomcat总体架构

1. Servlet容器

​ 容器:是一类组件的总称,例如后面的引擎Engine容器,应用Context容器,作用:处理请求返回响应数据。

​ Http服务器请求处理的过程:

​ 浏览器发给服务端的是一个HTTP格式的请求,服务器收到这个请求后,需要调用服务端程序来处理,所谓的服务端程序就是你写的Java类(servlet),一般来说不同的请求需要由不同的Java类(servlet)来处理。

在这里插入图片描述

​ Tomcat不直接调用业务类,而是把请求交给容器来处理,从而达到解耦合的目的。Servlet容器通过Servlet接口调用业务类,Tomcat按照Servlet规范的要求实现了Servlet容器,同时它们也具有HTTP服务器的功能。作为Java程序员,如果我们要实现新的业务功能,只需要实现一个Servlet,并把它注册到Tomcat(Servlet容器)中,剩下的事情就由Tomcat帮我们处理了。

​ Servlet接口和Servlet容器这一整套规范叫作Servlet规范。

2. Servlet容器工作流程

​ 把请求交给Servlet容器来处理,那么Servlet容器工作步骤为:

  1. 当客户请求某个资源时,请求信息发送给HTTP服务器一个Request对象数据,然后服务器会用一个ServletRequest对象把客户的请求信息封装起来。
  2. 调用Servlet容器的service方法,然后根据Web应用配置文件中的请求的Url路径和Servlet的映射关系,找到Servlet,没有就根据反射机制动态创建这个Servlet,调用init方法完成初始化,接着调用servlet的service方法处理请求(根据get,或者post来进行处理),然后将封装好的ServletResponse对象返回给HTTP服务器,然后服务器再返回给客户端Response对象。

在这里插入图片描述

​ 综上所述,我们就了解了需求,了解了Tomcat服务器的核心功能:处理连接加载Servlet处理Requst请求

3. Tomcat整体架构

​ Tomcat要实现两个核心功能:
1) 开启Socket连接,监听服务器端口,负责网络字节流与Request和Response对象的转化。
2) 加载和管理Servlet,以及具体处理Request请求。

​ 为了方便解耦,例如适配多种网络协议但请求处理却相同的时候,Tomcat设计了两个组件来实现这两个功能,连接器(Connector)和容器(Container)。

连接器负责对外交流,处理Socket连接,容器(引擎Engine)负责加载和管理Servlet,和内部逻辑处理。

在这里插入图片描述

注意:Tomcat架构的重点便是连接器和容器内部的具体架构和源码分析。

4. Connector和Container

1. Container容器

​ 容器只是个统称,其实下面是继承实现了Engine、Host、Context、Wrapper(Servlet)。Container可以添加并且维护这些个子容器,并且是弱依赖的关系,根据不同级别来加入处理请求的组件,例如8.56之前直接由Service维护一个Context。

Lifecycle:所有的组件均存在初始化、启动、停止等生命周期方法,拥有生命周期的性质,所以我们又可以进行一次接口抽象。

​ Tomcat核心的组件默认实现均是继承自LifecycleMBeanBase抽象类,可以进行组件状态转换和维护(自身注册为MBean,通过管理工具维护)。

在这里插入图片描述

2. Pipeline和Valve(针对Container组件)

​ 在增强组件灵活性和可扩展性方面,Tomcat采用了职责链模式来实现客户端请求的处理,这也是职责链模式的经典应用场景。

每次请求通过执行一个职责链来完成具体的请求处理,Tomcat定义了Pipeline管道和Valve阀两个接口,前者构建职责链,后者代表职责链上每个处理器。

Pipeline中维护了一个基础的Valve,始终位于末端,负责后序组件管道调用,封装了请求处理和输出响应,然后addValve通过头插法,添加到它之前,每次按照顺序执行。

在这里插入图片描述

3. Connector连接器

​ 接下来我们来细化一下连接器这个组件——Connector,连接器的功能包括:

  • 监听服务器的端口,例如http协议则为8080端口,读取来自客户端的请求
  • 将请求的数据按照对应的指定协议进行解析
  • 重点:将请求地址匹配到正确的容器进行处理
  • 返回响应

只有这样才能保证接收的客户端请求交由请求地址匹配的容器处理。

  • ProtocolHandler协议处理器

在Tomcat中,ProtocolHandler表示一个协议处理器,针对不同的协议和I/O方式,提供了不同的实现,例如Http11NioProtocol表示基于NIO的HTTP协议处理器。

在Connector启动时,Endpoint启动线程来监听服务器端口,接收到请求后调用Processor进行数据读取,根据请求地址映射(Servlet规范定义)通过Mapper实现到具体的容器进行处理。

  • CoyoteAdapter:适配器模式下Tomcat默认的Connector实现。

在这里插入图片描述

5. Bootstrap和Catalina

​ 1. Tomcat有几个重要的配置文件,其中最核心的就是server.xml。通过这个文件,可以修改Tomcat组件的配置参数,甚至添加组件,后序展开。

​ 2. Tomcat通过Catalina中的Digester来解析XML文件,包括server.xmlweb.xml等等,最后提供了Bootstrap作为了应用服务器的启动入口,负责创建Catalina实例,根据Catalina来完成服务器的操作。

注意:Bootstrap在bin目录下,与Tomcat服务器完全的松耦合,通过反射来创建Catalina实例,用于构造整个服务器。好处:Bootstrap实现了启动入口与核心环节的解耦,简化启动(避免各种依赖),更加灵活的组织我们的中间件产品结构。

在这里插入图片描述

三、Coyote连接器

Coyote 封装了底层的网络通信(Socket 请求及响应处理),为Servlet容器Catalina提供了统一的接口。

​ Coyote 作为独立的模块,只负责具体协议和IO的相关操作, 与Servlet 规范实现没有直接关系,因此即便是 Request 和 Response 对象也并未实现Servlet规范对应的接口, 而是在Catalina 中将他们进一步封装为ServletRequest 和 ServletResponse。

在这里插入图片描述

1. 协议与I/O模型

​ 在Coyote中支持多种应用层协议和I/O模型:

在这里插入图片描述

​ 在 8.0 之前,Tomcat 默认采用的I/O方式为 BIO,之后改为 NIO。无论 NIO、NIO2 还是 APR,在性能方面均优于以往的BIO。

在这里插入图片描述

​ Tomcat支持多种协议与I/O,则一个容器对接多个连接器,多个连接器和容器组装后成为一个Service组件,同时Tomcat可以配置多个Service,来使得更加灵活。实现通过不同的端口号来访问同一台机器上部署的不同应用。

2. 连接器组件

​ 连接器各个组件的作用如下图,如二、Tomcat总体架构中的Connector连接器所述:

连接器内部维护一个ProtocolHandler协议处理器和Adapter适配器,来分别进行请求监听和处理和封装request对象成容器能够解析的servletRequest。

在这里插入图片描述

1. EndPoint监听点

​ 监听即通信监听,EndPoint作为Coyote的通信端点,即通信监听的端口。

注意:EndPoint是具体Socket接受和发送处理器,是对传输层的一层抽象,用TCP/IP协议来实现的。

​ Tomcat提供的是抽象类AbstractEndpoint , 里面定义了两个内部类:Acceptor和SocketProcessor。

  • Acceptor用于监听Socket连接请求,继承了Runnable,本质就是个线程,一直死循环监听连接

  • SocketProcessor用于处理接收到的Socket请求,它实现Runnable接口,在Run方法里调用协议处理组件Processor进行处理。

    为了提高处理能力,SocketProcessor被提交到线程池来执行。而这个线程池叫作执行器(Executor)

public abstract static class Acceptor implements Runnable {
public enum AcceptorState {
NEW, RUNNING, PAUSED, ENDED
}

protected volatile AcceptorState state = AcceptorState.NEW;
public final AcceptorState getState() {
return state;
}

private String threadName;
protected final void setThreadName(final String threadName) {
this.threadName = threadName;
}
protected final String getThreadName() {
return threadName;
}
}

2. Processor处理接口

​ Coyote的协议处理的接口,如果说EndPoint是出来TCP/IP协议的,那么Processor用来实现Http协议的,是对应用层的抽象。

功能:接收前者的Socket -> 转换成字节流解析成为Request,然后将Requst对象通过适配器Adapter将其提交到容器来处理。

3. ProtocolHandler协议处理器

​ 在第二、Tomcat总体架构中,我们知道了ProtocolHandler是对 EndPoint监听点 和 Processor协议 处理的一层封装,根据协议和I/O提供了实现类:

AjpNioProtocol , AjpAprProtocol, AjpNio2Protocol , Http11NioProtocol ,Http11Nio2Protocol , Http11AprProtocol。

​ 一般我们在配置文件server.xml中进行配置,指定对应的协议名称,默认使用Http11NioProtocol

<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />

4. Adapter适配器

​ Tomcat定义了自己的Request类来“存放”各种各样的请求信息,前面讲到的ProtocolHandler解析请求,然后生成Tomcat的Requst类,但是容器中需要的是ServletRequest对象。

​ Tomcat解决方案:引入CoyoteAdapter,这是适配器模式的经典运用,连接器调用CoyoteAdapter的Sevice方法,传入的是Tomcat Request对象,CoyoteAdapter负责将Tomcat Request转成ServletRequest,再调用容器的Service方法。

功能:将协议处理器解析出的Tomcat Request对象,适配成ServletRequest,再调用ServletRequest的Service方法,同理容器给的ServletResponse转换成Response。

3. 线程池

​ Tomcat的线程池,是在连接器Connector部分的用到的,下图是Tomcat线程池的执行流程

在这里插入图片描述

监听:

  • LimitLatch(类似于JUC里面的信号量Semaphore):顾名思义,是用来限流的,用来控制最大的连接数,防止服务器被压垮。
  • Acceptor 只负责 接收新的Socket连接,本质就是个线程,一直死循环执行监听Socket连接
  • Poller也是个线程,也是一直死循环监听SocketChannel通道是否有 可读的I/O事件(现在是服务器端,所以读就是浏览器传数据过来),发现有,则将其封装成任务对象socketProcessor,交给线程池处理。

处理:

  • Executor线程池中的 worker工作线程最终负责 处理请求。

四、Catalina容器

​ Catalina 是Servlet 容器实现,包含了之前讲到的所有的容器组件。

在这里插入图片描述

​ Tomcat 本质上就是一款 Servlet 容器, 因此Catalina 才是 Tomcat 的核心 , 其他模块都是为Catalina 提供支撑的。 比如 : 通过Coyote 模块提供链接通信,Jasper 模块提供JSP引擎。其实在Tomcat源码中也有体现:

​ 在java.org.apache包下

在这里插入图片描述

1. Catalina结构

​ Catalina的主要组件结构如下:

在这里插入图片描述

​ Catalina管理这Server,而Server是表示整个服务器的,每个Server下面都有很多个Service服务,每个服务都包含着多个连接器组件Connector(Coyote实现的)和一个容器组件Container。在Tomcat启动时,就会初始化一个Catalina的实例。

总结:所以Servlet容器Catalina = 1个Server(Server下面有多个Service,每个Service下面又有多个连接器Connector + 容器Container)

2. Catalina组件

在这里插入图片描述

​ Catalina容器各个组件的功能:

  • Catalina:负责解析Tomcat的配置文件 , 以此来创建服务器Server组件,并根据命令来对其进行管理
  • Server:整个Catalina Servlet容器以及其它组件,负责组装并启动Servlet引擎,Tomcat连接器。Server通过实现Lifecycle接口,提供了一种优雅的启动和关闭整个系统的方式(职责链模式)
  • Service:服务是Server内部的组件,一个Server包含多个Service。它将若干个Connector组件绑定到一个Container(Engine)上(多个连接器 —— 一个容器)
  • Connector:处理与客户端的通信,它负责监听接收客户请求,然后转给相关的容器处理,最后向客户返回响应结果
  • Container:负责处理用户的servlet请求,并返回对象给web用户的模块

3. Container容器

​ Tomcat设计了4种容器:分别是Engine,Host,Context 和 Wrapper(Container是他们的盖称)。但是这四种容器是父子继承关系,Tomcat通过一种分层的架构,使得Servlet容器具有很好的灵活性:

在这里插入图片描述

​ Container各个组件的含义 :

  • Engine:Catalina的Servlet引擎,来管理多个虚拟的主机,每个Service一个Engine,但是可以包含多个虚拟主机Host

  • Host:代表一个虚拟主机,或者说一个站点,可以给Tomcat配置多个虚拟主机地址,而一个虚拟主机下可包含多个Context

  • Context:表示一个Web应用程序, 一个Web应用可包含多个Wrapper

  • Wrapper:Servlet的别名,Wrapper 作为容器中的最底层,不能包含子容器

4. server.xml结构

<Server>
<Service>
<connector>......</connector>
<Engine name="Catalina" defaultHost="localhost">

<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<context>......</context>
</Host>

</Engine>
</Service>
</Server>

我们也可以再通过Tomcat的server.xml配置文件来加深对Tomcat容器的理解。Tomcat采用了组件化的设计,它的构成组件都是可配置的,其中最外层的是Server,其他组件按照一定的格式要求配置在这个顶层容器中。Tomcat用组合模式来管理这些容器的。

在这里插入图片描述

​ 具体实现方法是,所有容器组件都实现了Container接口,因此组合模式可以使得用户对Wrapper和Context、Host或者Engine的使用具有一致性。

​ Container接口扩展了LifeCycle接口,LifeCycle接口用来统一管理各组件的生命周期.

在这里插入图片描述

五、Tomcat启动流程

1. 启动流程分析

​ 前面我们已经讲到Tomcat的启动入口Bootstrap(来创建Catalina实例)、Shell程序Catalina的以及各个组件的关系。

在这里插入图片描述

所以Tomcat的启动步骤:

(1)启动Tomcat,需要调用的是bin路径下面的startup.bat(linux是startup.sh),启动脚本后调用了Catalina.bat

(2)在Catalina.bat脚本中是调用了Bootstrap里面的main方法。

(3)main方法调用init方法来初始化Catalina和类加载器。

(4)main方法然后调用load方法,在其中调用Catalina的load方法。

(5)Catalina的load需要对组件进行初始化,这时就要用到里面的一个内部类Digester对象来解析server.xml

(6)然后调用后序组件的init初始化操作,加载Tomcat的配置文件,初始化容器组件,监听端口号,准备接受客户端的请求。

2. Lifecycle

​ 二、Tomcat总体架构 中,我们提到了Lifecycle这个接口,因为组件均存在初始化、启动、停止等生命周期方法,所以要拥有生命周期管理

包括组件 Server、Service、Container、Executor、Connector,都实现了一个生命周期的接口,从而具有了以下生命周期中的核心方法:

在这里插入图片描述

3. 组件的默认实现

​ 对于Endpoint而言,Tomcat没有对应的Endpoint接口,但有一个抽象类AbstractEndpoint,有NioEndpoint、 Nio2Endpoint、AprEndpoint,分别对应着Coyote连接器中支持的三种IO模型:NIO、NIO2、APR,默认是NioEndpoint。

在这里插入图片描述

​ ProtocolHandler : Coyote协议接口,通过封装Endpoint和Processor , 实现针对具体协议的处理功能。Tomcat按照协议和IO提供了6个实现类。

注意:实际上Tomcat的默认组件 = ProtocolHandler默认实现(例如Http11NioProtocol) + Standard组件(例如StandardServer、StandardEngine、StandardContext)

1) AjpNioProtocol :采用NIO的IO模型。
2) AjpNio2Protocol:采用NIO2的IO模型。
3) AjpAprProtocol :采用APR的IO模型,需要依赖于APR库。

1) Http11NioProtocol :采用NIO的IO模型,默认使用的协议(如果服务器没有安装APR)。
2) Http11Nio2Protocol:采用NIO2的IO模型。
3) Http11AprProtocol :采用APR的IO模型,需要依赖于APR库。

在这里插入图片描述

4. 源码解析

​ 分析源码先从启动脚本startup.bat开始分析

(1)startup.bat实际上是调用的catalina的启动脚本catalina.bat

在这里插入图片描述

(2)在catalina.bat中调用的是BootStrap的Main方法,MainClass:BootStrap —-> main(String[] args),所以我们去看源码

在这里插入图片描述

1. BootStrap启动类

​ 查看Tomcat的源代码:BootStrap先进行init方法,然后通过反射创建Catalina对象和调用load方法,实际上是Catalina的load方法,对组件进行初始化,都是通过反射执行。

/**
* Main method and entry point when starting Tomcat via the provided
* scripts.
*
* @param args Command line arguments to be processed
*/
public static void main(String args[]) {

if (daemon == null) {
// Don't set daemon until init() has completed
//1. 先初始化Bootstrap对象,调用init方法
Bootstrap bootstrap = new Bootstrap();
try {
// 2.通过反射创建了一个Catalina对象
bootstrap.init();
} catch (Throwable t) {
/*...................................................................*/
}
daemon = bootstrap;
} else {
/*...................................................................*/
}

try {
// 命令参数
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
/*...................................................................*/
// 如果是启动(start)
} else if (command.equals("start")) {
// 3.进入此,调用bootstrap的load和start方法
daemon.setAwait(true);
//调用load方法,对组件进行初始化
daemon.load(args);
daemon.start();
/*...................................................................*/
}
/*...................................................................*/

}

/**
* Load daemon.
*/
private void load(String[] arguments)
throws Exception {

// Call the load() method
String methodName = "load";
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
// 通过反射执行Catalina的load方法
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled())
log.debug("Calling startup class " + method);
// methodName是load
// 4.调用catalina的load方法
method.invoke(catalinaDaemon, param);
}

2. Catalina类load方法

​ 直接进入Catalina类的load方法:先是通过Digerster解析核心的配置文件(因为这样才能去加载Server后面的组件),然后调用Server的init,然后逐级init。

/**
* Start a new server instance.
*/
public void load() {

/*...................................................................*/
// Before digester - it may be needed
initNaming();

// Create and execute our Digester

// Digester是Tomcat的XML配置文件解析工具
// 5.使用Digester去解析server.xml
Digester digester = createStartDigester();

/*...................................................................*/

// Start the new server
try {
// 6.调用server的init方法
getServer().init();
} catch (LifecycleException e) {
/*...................................................................*/
}
/*...................................................................*/
}

3.组件的init初始化

​ 组件们在实现类中,都是调用了Standard组件名 或者是Standard组件名 的initInternal。

/**
* Invoke a pre-startup initialization. This is used to allow connectors
* to bind to restricted ports under Unix operating environments.
*/
@Override
protected void initInternal() throws LifecycleException {
super.initInternal();
/*...................................................................*/
// Initialize our defined Services
for (int i = 0; i < services.length; i++) {
// services进行初始化
services[i].init();
}
}

​ 在Service的实现类里面也是用到了模板方法设计模式,调用initInternal(),然后初始化了engine、executor、connector。

然后Engine再去初始化后序的host,context,Connector再去初始化后序的Adapter、ProtocolHandler的init,然后继续下去EndPoint的init。

/**
* Invoke a pre-startup initialization. This is used to allow connectors
* to bind to restricted ports under Unix operating environments.
*/
@Override
protected void initInternal() throws LifecycleException {

super.initInternal();

if (engine != null) {
// 调用engine的init方法
engine.init();

// Initialize any Executors
for (Executor executor : findExecutors()) {
if (executor instanceof JmxEnabled) {
((JmxEnabled) executor).setDomain(getDomain());
}
// 线程池
executor.init();
}

// Initialize mapper listener
mapperListener.init();

// Initialize our defined Connectors
synchronized (connectorsLock) {
for (Connector connector : connectors) {
try {
// connector的init
connector.init();
} catch (Exception e) {
String message = sm.getString(
"standardService.connector.initFailed", connector);
log.error(message, e);

if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
throw new LifecycleException(message);
}
}
}
}

4. 组件的start启动

​ 当Bootstrap启动类的init以及后序组件的init初始化后,Bootstrap启动类再调用start方法

// 3.进入此,调用bootstrap的load和start方法
daemon.setAwait(true);
daemon.load(args);
daemon.start();

​ Server和Service都是调用了StandardServer或者是StandardService的initInternal或者startInternal

在这里插入图片描述

在这里插入图片描述

5. 启动总结

​ 从启动流程图中以及源码中,我们可以看出Tomcat的启动过程非常标准化, 统一按照生命周期管理接口Lifecycle的定义进行启动。首先调用init() 方法进行组件的逐级初始化操作,然后再调用start()方法进行启动。

每一级的组件除了完成自身的处理外,还要负责调用子组件响应的生命周期管理方法,组件与组件之间是松耦合的,因为我们可以很容易的通过配置文件进行修改和替换。

六、Tomcat请求处理流程(执行)

​ Tomcat的层次很多,那么确定每一个请求应该由哪个Wrapper容器里的Servlet来处理,这就是一个问题。也就是连接器生成的servletrequest是如何找到容器中对应的servlet来处理,Tomcat是用Mapper组件来完成这个任务的。

1. Mapper组件

Mapper组件里保存了Web应用的配置信息,其实就是容器组件与请求URL里的域名和路径的映射关系,比如Host容器里配置的域名、Context容器里的Web应用路径,以及Wrapper容器里Servlet映射的路径,你可以想象这些配置信息就是一个多层次的Map。

​ 下面的示意图中,就描述了当用户请求链接 http://www.itcast.cn/bbs/findAll 之后, 是如何找到最终处理业务逻辑的servlet。

通过端口8080确定是Http请求,根据对应的IO类型,从而确定连接器,然后通过域名host找到对应的引擎(虚拟主机),引擎下面同时对应appbase(context的路径):即webapps,进入对应的context里面的web.xml中,通过映射关系找到对应的Wrapper,然后构造过滤器链FilterChain,执行每个Filter,最后转换成Servlet。

在这里插入图片描述

2. 请求处理流程分析

在这里插入图片描述

步骤如下:

1) Connector组件Endpoint中的Acceptor监听客户端Socket连接并接收Socket。将连接交给线程池Executor处理,开始执行请求响应任务。Processor组件读取消息报文,解析请求行、请求体、请求头,封装成Request对象。
2) CoyoteAdaptor适配器组件负责将Connector组件和Engine容器关联起来,把Processor生成的Request生成对应的ServletRequset传递到Engine容器中,调用 Pipeline。
3) Mapper组件根据请求行的URL值和请求头的Host值匹配由哪个Host容器、Context容器、Wrapper容器处理请求。
4) Engine容器的管道开始处理,管道中包含若干个Valve、每个Valve负责部分处理逻辑。执行完Valve后会执行基础的Valve–StandardEngineValve,负责调用Host容器的Pipeline。Host、Context、Wrapper容器流程类似,最后执行Wrapper容器对应的Servlet对象的处理方法
5) 构造一个过滤器链FilterChain,然后执行各个Filter,最后执行Servlet.service()

3. 源码分析

在这里插入图片描述

1)发起请求后,Tomcat的NioEndpoint中的Acceptor就会监听到Socket连接,然后调用内部维护的线程池Executor的executor方法执行请求。executor方法会调用Processor

protected class Acceptor extends AbstractEndpoint.Acceptor {

@Override
public void run() {
/*............................................................*/
SocketChannel socket = null;
try {
// Accept the next incoming connection from the server
// serverSock为ServerSocketChannel类型
socket = serverSock.accept();
} catch (IOException ioe) {
// We didn't get a socket
countDownConnection();
if (running) {
// Introduce delay if necessary
errorDelay = handleExceptionWithDelay(errorDelay);
// re-throw
throw ioe;
} else {
break;
}
}
// Successful accept, reset the error delay
errorDelay = 0;

// Configure the socket
if (running && !paused) {
// setSocketOptions() will hand the socket off to
// an appropriate processor if successful
if (!setSocketOptions(socket)) {
closeSocket(socket);
}
} else {
closeSocket(socket);
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("endpoint.accept.fail"), t);
}
}
state = AcceptorState.ENDED;
}
protected class SocketProcessor extends SocketProcessorBase<NioChannel> {

public SocketProcessor(SocketWrapperBase<NioChannel> socketWrapper, SocketEvent event) {
super(socketWrapper, event);
}

@Override
protected void doRun() {
NioChannel socket = socketWrapper.getSocket();
SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());

try {
// 请求之前的确认握手操作
int handshake = -1;

try {
if (key != null) {
if (socket.isHandshakeComplete()) {
// No TLS handshaking required. Let the handler
// process this socket / event combination.
handshake = 0;
} else if (event == SocketEvent.STOP || event == SocketEvent.DISCONNECT ||
event == SocketEvent.ERROR) {
// Unable to complete the TLS handshake. Treat it as
// if the handshake failed.
handshake = -1;
} else {
handshake = socket.handshake(key.isReadable(), key.isWritable());
// The handshake process reads/writes from/to the
// socket. status may therefore be OPEN_WRITE once
// the handshake completes. However, the handshake
// happens when the socket is opened so the status
// must always be OPEN_READ after it completes. It
// is OK to always set this as it is only used if
// the handshake completes.
event = SocketEvent.OPEN_READ;
}
}
} catch (IOException x) {
handshake = -1;
if (log.isDebugEnabled()) log.debug("Error during SSL handshake",x);
} catch (CancelledKeyException ckx) {
handshake = -1;
}
if (handshake == 0) {
SocketState state = SocketState.OPEN;
// Process the request from this socket
if (event == null) {
// 请求的处理,执行Processor的process方法
state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
} else {
state = getHandler().process(socketWrapper, event);
}
if (state == SocketState.CLOSED) {
close(socket, key);
}
} else if (handshake == -1 ) {
close(socket, key);
} else if (handshake == SelectionKey.OP_READ){
socketWrapper.registerReadInterest();
} else if (handshake == SelectionKey.OP_WRITE){
socketWrapper.registerWriteInterest();
}
} catch (CancelledKeyException cx) {
socket.getPoller().cancelledKey(key);
} catch (VirtualMachineError vme) {
ExceptionUtils.handleThrowable(vme);
} catch (Throwable t) {
log.error("", t);
socket.getPoller().cancelledKey(key);
} finally {
socketWrapper = null;
event = null;
//return to cache
if (running && !paused) {
processorCache.push(this);
}
}
}
}

2)Processor组件处理Http请求,解析Socket请求,读取消息报文,解析请求行、请求体、请求头,封装成Request对象。

处理完后,调用CoyoteAdapter的Service方法,把刚才解析的request和response传进去

在这里插入图片描述

3)到了CoyoteAdapter的Service方法,调用容器connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

在这里插入图片描述

@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
throws Exception {

Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);

if (request == null) {
// Create objects
request = connector.createRequest();
request.setCoyoteRequest(req);
response = connector.createResponse();
response.setCoyoteResponse(res);

// Link objects
request.setResponse(response);
response.setRequest(request);

// Set as notes
req.setNote(ADAPTER_NOTES, request);
res.setNote(ADAPTER_NOTES, response);

// Set query string encoding
req.getParameters().setQueryStringCharset(connector.getURICharset());
}

if (connector.getXpoweredBy()) {
response.addHeader("X-Powered-By", POWERED_BY);
}

boolean async = false;
boolean postParseSuccess = false;

req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());

try {
// Parse and set Catalina and configuration specific
// request parameters
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
//check valves if we support async
request.setAsyncSupported(
connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
}
if (request.isAsync()) {
async = true;
ReadListener readListener = req.getReadListener();
if (readListener != null && request.isFinished()) {
// Possible the all data may have been read during service()
// method so this needs to be checked here
ClassLoader oldCL = null;
try {
oldCL = request.getContext().bind(false, null);
if (req.sendAllDataReadEvent()) {
req.getReadListener().onAllDataRead();
}
} finally {
request.getContext().unbind(false, oldCL);
}
}

Throwable throwable =
(Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

// If an async request was started, is not going to end once
// this container thread finishes and an error occurred, trigger
// the async error process
if (!request.isAsyncCompleting() && throwable != null) {
request.getAsyncContextInternal().setErrorState(throwable, true);
}
} else {
request.finishRequest();
response.finishResponse();
}

} catch (IOException e) {
// Ignore
} finally {
AtomicBoolean error = new AtomicBoolean(false);
res.action(ActionCode.IS_ERROR, error);

if (request.isAsyncCompleting() && error.get()) {
// Connection will be forcibly closed which will prevent
// completion happening at the usual point. Need to trigger
// call to onComplete() here.
res.action(ActionCode.ASYNC_POST_PROCESS, null);
async = false;
}

// Access log
if (!async && postParseSuccess) {
// Log only if processing was invoked.
// If postParseRequest() failed, it has already logged it.
Context context = request.getContext();
Host host = request.getHost();
// If the context is null, it is likely that the endpoint was
// shutdown, this connection closed and the request recycled in
// a different thread. That thread will have updated the access
// log so it is OK not to update the access log here in that
// case.
// The other possibility is that an error occurred early in
// processing and the request could not be mapped to a Context.
// Log via the host or engine in that case.
long time = System.currentTimeMillis() - req.getStartTime();
if (context != null) {
context.logAccess(request, response, time, false);
} else if (response.isError()) {
if (host != null) {
host.logAccess(request, response, time, false);
} else {
connector.getService().getContainer().logAccess(
request, response, time, false);
}
}
}

req.getRequestProcessor().setWorkerThreadName(null);

// Recycle the wrapper request and response
if (!async) {
updateWrapperErrorCount(request, response);
request.recycle();
response.recycle();
}
}
}

4)engine后序的组件Host、Context、Wrapper都是先拿到管道,再getFirst拿到valve阀(因为是头插法式的add)

在这里插入图片描述

在这里插入图片描述

都是调用对应StandardXXX的invoke方法

在这里插入图片描述

5)最后执行Wrapper容器对应的allocate处理方法,拿到Servlet对象

在这里插入图片描述

​ Tomcat中的各个组件各司其职,组件之间松耦合,Tomcat中定义了Pipeline 和 Valve 两个接口,Pipeline 用于构建责任链,只需要调用第一个处理器,它就会依次执行。