美文网首页
Tomcat 学习

Tomcat 学习

作者: CSeroad | 来源:发表于2022-10-21 17:13 被阅读0次

前言

在学习内存马之前,了解tomcat的运行原理是十分有必要的。

Tomcat 整体架构

Tomcat 结构比较复杂,我们先从重点的结构上分别学习各个模块。

image.png

Tomcat 中最顶层的容器是Server,一个Server包含多个Service,一个Service只能有一个Container ,但可以有多个Connector。

Service主要用来提供对外服务,包含两个部分:Connector 连接器和Container 容器。

  • Connector 连接器用于处理连接相关的事情,并提供Socket与请求、响应之间的转换;
  • Container 容器用于封装和管理Servlet,以及具体处理Request请求;

Coyote 连接器

Coyote 是Tomcat 连接器Connector框架的名称,封装了底层的网络通信(Socket 请求及响应处理),为Servlet容器Catalina提供了统一的接口。
Coyote 中支持多种应用层协议和I/O模型。

IO 模型 描述
NIO 非阻塞同步I/O,采用Java NIO类库实现。
NIO2 非阻塞异步I/O,采用JDK 7最新的NIO2类库实现。
APR 采用Apache可移植运行库实现,是C/C++编写的本地库。需要单独安装APR库。
应用层协议 描述
HTTP/1.1 常见的Web访问协议
AJP 用于和Web服务器集成(如Apache),以实现对静态资源的优化以及集群部署,当前支持AJP/1.3。
HTTP/2 HTTP 2.0 下一代HTTP协议

连接器组件如下:

图片来源黑马程序员

1、EndPoint 监听点
Coyote 通信端点,即通信监听的接口,是具体Socket接收和发送处理器,用来实现TCP/IP协议。
2、Processor 处理接口
Processor接收来自EndPoint的Socket,读取字节流解析为Tomcat Request和Response对象,实现HTTP协议。
3、ProtocolHandler 协议处理器
通过Endpoint 和 Processor,实现针对具体协议的处理能力。
4、Adapter 适配器
将ProtocolHandler 协议处理器解析出的Tomcat Request对象,适配成ServletRequest,再调用ServletRequest的Service方法。

Container 容器

Container 用于封装和管理Servlet,以及具体处理Request请求,在Container内部包含了4个子容器。
如下图:

图片来源黑马程序员

1、Engine
Catalina的Servlet引擎,用来管理多个虚拟的主机,每个Service一个Engine,但是可以包含多个虚拟主机Host。
2、Host
代表一个虚拟主机,可以给Tomcat配置多个虚拟主机地址,而一个虚拟主机下可包含多个Context。
3、Context
表示一个Web应用程序,一个Web应用可包含多个Wrapper。
4、Wrapper
Servlet的别名,是容器中的最底层。

server.xml 配置文件可以充分说明这个容器结构。

<Server>
  <Service>
    <Engine>
      <Host>
        <Context></Context>
      </Host>
    </Engine>
  </Service>
</Server>

Catalina 容器

在前文中多次提到Catalina容器,那么它又是做什么的呢?
Catalina负责管理Server,Server表示着整个服务器。也可以理解为一个Server就是一个Catalina。
如下图:

图片来源黑马程序员
组件 职责
Catalina 负责解析Tomcat的配置文件,以此来创建服务器Server组件
Server 表示整个Catalina Servlet容器以及其它组件,负责组装并启动Servlet引擎,Tomcat连接器。
Service 一个Server包含多个Service。将若干个Connector 绑定到一个Container 容器(Engine)上。
Connector 连接器,用于处理连接相关的事情,并提供Socket与请求、响应之间的转换。
Container 容器,负责处理用户的servlet请求,并返回对象给Web用户。

Tomcat 源码安装

下载源码解压后,创建home文件夹,并将conf、wappers目录移到该文件夹中。
创建pom.xml文件。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>apache-tomcat-8.5.73-src</artifactId>
    <name>Tomcat8.5</name>
    <version>8.5</version>

    <build>
        <finalName>Tomcat8.5</finalName>
        <sourceDirectory>java</sourceDirectory>
        <!-- <testSourceDirectory>test</testSourceDirectory> -->
        <resources>
            <resource>
                <directory>java</directory>
            </resource>
        </resources>
        <!-- <testResources>
           <testResource>
                <directory>test</directory>
           </testResource>
        </testResources>-->
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.easymock</groupId>
            <artifactId>easymock</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>ant</groupId>
            <artifactId>ant</artifactId>
            <version>1.7.0</version>
        </dependency>
        <dependency>
            <groupId>wsdl4j</groupId>
            <artifactId>wsdl4j</artifactId>
            <version>1.6.2</version>
        </dependency>
        <dependency>
            <groupId>javax.xml</groupId>
            <artifactId>jaxrpc</artifactId>
            <version>1.1</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jdt.core.compiler</groupId>
            <artifactId>ecj</artifactId>
            <version>4.5.1</version>
        </dependency>
      <dependency>
        <groupId>org.apache.ant</groupId>
        <artifactId>ant</artifactId>
        <version>1.7.0</version>
      </dependency>

    </dependencies>
</project>

再以maven源码导入。

image.png

配置启动类。

-Dcatalina.home=/Users/cseroad/IdeaProjects/Tomcat_Project/apache-tomcat-8.5.73-src/home
-Dcatalina.base=/Users/cseroad/IdeaProjects/Tomcat_Project/apache-tomcat-8.5.73-src/home
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=/Users/cseroad/IdeaProjects/Tomcat_Project/apache-tomcat-8.5.73-src/home/conf/logging.properties

org.apache.catalina.startup下,找到Bootstrap类的main方法开始启动。
访问tomcat如果出现500错误,需要在org.apache.catalina.startup包下的ContextConfig类中
configureStart方法添加

// 初始化jsp解析器
context.addServletContainerInitializer(new JasperInitializer(), null);

再次重启即可。

image.png

请求处理

先创建一个Servlet并配置web.xml,然后编译请求地址,再整个servlet_war_exploded拷贝出来。部署在Tomcat上并以源码的形式进行追踪分析请求过程。
web.xml

    <servlet>
        <servlet-name>BbsServlet</servlet-name>
        <servlet-class>com.itcast.web.BbsServlet</servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>BbsServlet</servlet-name>
        <url-pattern>/bbs/findAll</url-pattern>
    </servlet-mapping>

BbsServlet

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class BbsServlet extends HttpServlet {
    public BbsServlet() {
    }

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("GET请求");
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("post请求");
    }
}
image.png

在启动Tomcat时,Tomcat会先初始化。
然后endpoint准备完毕,等待外部的请求连接。
org.apache.tomcat.util.net.NioEndpoint类内部的Acceptor类的run方法开始

image.png

处理接收到的socket对象,获取输入输出流,然后注册到Poller当中,添加到PollerEvent队列当中。

image.png

org.apache.tomcat.util.net的register方法。

image.png

而后Poller进行处理socket,调用org.apache.tomcat.util.net.NioEndpoint的processKey方法处理读写。

image.png image.png

里面就到org.apache.tomcat.util.net.AbstractEndpoint.processSocket的Executor实现的线程池进行处理。

image.png

提交到线程池,放入线程池的 workQueue 中。
再调用org.apache.tomcat.util.net.NioEndpoint.SocketProcessor的doRun()方法。

image.png

org.apache.coyote.AbstractProtoco.ConnectionHandler的process方法处理socket请求。

image.png

该方法里面创建一个Http11Processor,解析socket请求。
这样Socket中的内容就会封装到Request 中。

通过getAdapter获取合适的处理器,调用service方法进行处理。执行的是org.apache.catalina.connector.CoyoteAdapter类的 service 方法。

image.png

创建了最终我们使用的ServletRequest和ServletResponse。
来到org.apache.catalina.connector.CoyoteAdapter类的service方法。
然后调用容器,getContainer方法会拿到service关联的Engine。

connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);

先将请求传递到 Engine 管道中,传递到Engine Valve 这个阀中。
调用org.apache.catalina.core.StandardEngineValve下的invoke方法。

image.png

获取host,接着invoke方法里面为

host.getPipeline().getFirst().invoke(request, response);

表示请求从 Engine Valve 传递到一个 Host 的管道中,在该管道中最后传递到 Host Valve 这个阀里。
调用org.apache.catalina.core.StandardHostValve下的invoke方法。

image.png

获取context,接着invoke方法里面为

context.getPipeline().getFirst().invoke(request, response);

表示从 Host Valve 传递到 Context 的管道中,在该管道中最后传递到 Context Valve 中。
调用org.apache.catalina.core.StandardContextValve下的invoke方法。

image.png

获取wrapper,接着invoke方法里面为

wrapper.getPipeline().getFirst().invoke(request, response);

表示请求传递到 Wrapper Valve 中,在这里会经过一个过滤器链 Filter Chain ,最终到熟悉的Servlet中。调用org.apache.catalina.core.StandardWrapperValve下的invoke方法。

image.png

创建Servlet,再往下创建filterChain过滤器链。org.apache.catalina.core.StandardWrapperValve下的invoke方法。

image.png

而后执行过滤器链,org.apache.catalina.core.StandardWrapperValve下的invoke方法。

image.png

执行org.apache.catalina.core.ApplicationFilterChain 下的 doFilter 方法。

image.png

往下调用internalDoFilter方法,实际调用的org.apache.catalina.core.ApplicationFilterChain下的 internalDoFilter 方法。

image.png

到HttpServlet类的service方法,再调用我们编写的doGet()方法。

image.png

整个过程极为复杂,画张图来梳理一下。

image.png

总结:
endpoint收到socket请求,调用handler找到Processor,接着调用CoyoteAdapter,将socket请求处理为Request请求。这就是tomcat连接器的工作。接着CoyoteAdapter获取到Engine,Engine获取到host,host获取到Context,Context获取到Wrapper,Wrapper在反射调用就获取到了Servlet,然后创建过滤器、执行过滤器链,再执行Servlet。

其他配置

创建host

在server.xml中给Host添加别名,实现同一个Host拥有多个网络名称。

      <Host name="www.cseroad.tomcat"  appBase="webapps1"
            unpackWARs="true" autoDeploy="true">
            <Context docBase="E:\app" path="/app"  reloadable="true" ></Context>
      </Host>   

docBase:Web应用目录或者War包的部署路径。可以是绝对路径,也可以是相对于Host appBase的相对路径。
path:Web应用的Context 路径。

配置错误页面

在web.xml中配置

<error-page>
    <error-code>404</error-code>
    <location>/404.html</location>
</error-page>
<error-page>
    <error-code>500</error-code>
    <location>/500.html</location>
</error-page>

当出现404错误代码,就跳转至404.html自定义页面。

总结

针对Tomcat的源码学习有些吃力,整个过程需要结合代码、资料不断调试加深理解。

参考资料

https://blog.csdn.net/ly823260355/article/details/104181278
https://www.bilibili.com/video/BV1dJ411N7Um?p=5

相关文章

网友评论

      本文标题:Tomcat 学习

      本文链接:https://www.haomeiwen.com/subject/tqjpqrtx.html