美文网首页
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