Tomcat源码学习

环境搭建

我们研究的版本是tomcat9(之前spring版本的研究也对应的tomcat9)

源码下载

tomcat9下载传送门

环境配置

增加Maven的配置

在解压后的源码中放入一个内容如下的pom文件,其中的版本号跟随你自己下载的tomcat版本号自行调整

<?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>tomcat9</artifactId>
    <name>tomcat-9.0.41</name>
    <version>9.0.41</version>
    <build>
        <finalName>tomcat-9.0.41</finalName>
        <sourceDirectory>java</sourceDirectory>
        <!--<testSourceDirectory>test</testSourceDirectory>  test 下的有些文件报错,因此将test文件夹去掉了-->
        <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>3.6.0</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.0.2</version>
            </plugin>
        </plugins>
    </build>
     <dependencies>
        <dependency>
            <groupId>org.apache.ant</groupId>
            <artifactId>ant</artifactId>
            <version>1.9.5</version>
        </dependency>
         <!-- https://mvnrepository.com/artifact/biz.aQute.bnd/annotation -->
         <!-- https://mvnrepository.com/artifact/biz.aQute.bnd/biz.aQute.bndlib -->
         <dependency>
             <groupId>biz.aQute.bnd</groupId>
             <artifactId>biz.aQute.bndlib</artifactId>
             <version>5.2.0</version>
             <scope>provided</scope>
         </dependency>
 
         <!-- https://mvnrepository.com/artifact/org.apache.jasper/org.apache.jasper -->
         <!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-jasper -->
         <dependency>
             <groupId>org.apache.tomcat</groupId>
             <artifactId>tomcat-jasper</artifactId>
             <version>9.0.41</version>
         </dependency>
         <dependency>
            <groupId>org.apache.ant</groupId>
            <artifactId>ant-apache-log4j</artifactId>
            <version>1.9.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.ant</groupId>
            <artifactId>ant-commons-logging</artifactId>
            <version>1.9.5</version>
        </dependency>
        <dependency>
            <groupId>javax.xml.rpc</groupId>
            <artifactId>javax.xml.rpc-api</artifactId>
            <version>1.1</version>
        </dependency>
        <dependency>
            <groupId>wsdl4j</groupId>
            <artifactId>wsdl4j</artifactId>
            <version>1.6.2</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jdt.core.compiler</groupId>
            <artifactId>ecj</artifactId>
            <version>4.6.1</version>
        </dependency>
    </dependencies>
</project>
增加启动类

创建启动类,mainClass指定为tomcat的Bootstrap,增加以下的vm options解决乱码的问题

-Duser.language=en
-Duser.region=US
-Dfile.encoding=UTF-8
-Dsun.jnu.encoding=UTF-8

源码分析

整体架构

从conf下的server.xml开始分析(结合源码),逐步得到整体的包含关系,每一个标签都有对应的类(一般是接口,tomcat对其做了抽象封装)

<?xml version="1.0" encoding="UTF-8"?>

<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
  <!-- APR library loader. Documentation at /docs/apr.html -->
  <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <!-- Prevent memory leaks due to use of particular java/javax APIs-->
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />

  <!-- 实现JNDI的 Global JNDI resources
       Documentation at /docs/jndi-resources-howto.html
  -->
  <GlobalNamingResources>
    <!-- Editable user database that can also be used by
         UserDatabaseRealm to authenticate users
         指定一些资源的配置信息(比如数据库连接池)
    -->
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
  </GlobalNamingResources>
  
  <!-- Catalina(Tomcat的请求处理服务)这个服务使用Connector可以接受网络请求数据,处理请求  -->
  <Service name="Catalina">
    <!-- 
         代表我们需要去监听的端口
    -->
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <!-- Catalina Service是用来处理请求的服务  真正的会交给引擎去进行处理,控制整个的处理逻辑 -->
    <Engine name="Catalina" defaultHost="localhost">
      <!-- 认证信息 -->
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>
      <!-- 主机 -->
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />

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

整体架构图示

tomcat架构
tomcat详细架构

Server

server即是tomcat,其内包含一些监听器,可以指定一些资源的配置信息,最重要的是他包含了service进来

service

service服务包含了Connector连接器和Engine引擎两个重要组件

Connector

连接器能够进行端口的监听,获取到请求

Engine

引擎能够进行请求的处理,其内包含了Host

Host

虚拟主机,可以映射域名,它内部包含了Context

Context

他就是一个个的web应用(咱们部署的包),它内部包含了Wrapper

Wrapper

每一个Wrapper都封装了一个Servlet的配置详情

Tomcat的生命周期模板

Lifecycle

他是tomcat的生命周期接口,基本上上述组件都实现了他,类图如下:
Lifecycle

LifecycleBase

Lifecycle的具体实现类,利用模板模式,定义好整体的算法步骤,核心的实现留给组件自己去实现

Container

容器接口,上述的Host,Engine,Context,Wrapper等都是他的实现,容器可以继续添加子组件,并且容器中有一个特性是拥有一个管道Pipeline(请求必须要经历每一层容器的管道,里面的所有的阀门就可以来进行干预)

比如Host中的AccessLogValve日志阀门,可以用来记录请求的日志,阀门机制是Tomcat中,控制数据流向处理的核心,利用阀门来做类似Filter的预处理,阀门属于一个责任链的模式
容器的特点:

  • addChildren(Container child):有子容器
  • Container getParent():有父容器
  • Pipeline getPipeline():有管道
  • Pipeline里面有Valve阀门,阀门形成链式调用可以对请求进行预处理

Tomcat的启动流程

  1. BootStrap.init()方法:准备三个类加载器,拿到Catalina(反射创建实例)类
  2. 解析需要执行的命令(没有的话默认就是start):daemon.load(args)加载信息,通过反射执行catalina.load方法
  • 解析服务器的server.xml文件
  • 初始化输出流和错误流
  • getServer().init(),执行服务器的初始化方法
  1. daemon.start();执行服务器的启动方法,通过反射调用catalina.start方法
  • 拿到服务器,然后进入到服务器的启动流程getServer().start()
  • 跟服务器初始化流程基本一模一样,也是一层层的把上一步创建的组件调用其生命周期的启动方法
  • Engine的启动会找到所有的虚拟主机Host,把Host封装为StartChild(Callable类),交给线程池去执行,并发的启动
  1. 服务启动完毕后调用getServer().await(),一直while循环接收数据,监听8005端口,接收命令
启动流程图

Tomcat启动

BootStrap的初始化

首先调用BootStrap的init方法进行初始化(加了锁的),并且用volatile修饰了BootStrap的实例变量daemon

三个类加载器

准备common,server和shared三个类加载器(我实际断点调试时发现全部返回的是父类URLClassLoader类型)

commonLoader = createClassLoader("common", null);
            if (commonLoader == null) {
                // no config file, default to this loader - we might be in a 'single' env.
                commonLoader = this.getClass().getClassLoader();
            }
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
解析server.xml文件

配置文件的每一个标签就对应这一个类,创建对象封装好标签配置的属性内容,Host里面并不会在此时扫描加载Context应用组件(只是单纯的解析xml中的架构,按照层次封装到Catalina对象中)

服务器初始化

getServer().init()执行服务器的初始化,他最终调用到了子类StandardServerinitInternal()初始化方法,具体的步骤如下

  • globalNamingResources.init();JNDI功能初始化
  • 循环遍历service,调用service的初始化方法service.init()
  • service的初始化方法中,去调用Engine引擎的初始化方法engine.init()
  • Engine初始化之后,再让Connector连接器初始化connector.init()
  • 连接器的初始化重点是协议处理器的初始化protocolHandler.init():他最重要的是endpoint.init()端口的初始化,将ServerSocket绑定好
  • 至此层层返回,初始化流程整个完成

整个初始化的逻辑,是一层一层嵌套的,他的顺序就是上面讲过的包含关系,一层层传递下去

连接器初始化

connector在初始化时准备好了ProtocolHandler协议处理器和CoyoteAdapter适配器两个组件(新版本的tomcat默认采用的都是http/1.1 的NIO协议),之后他开始调用ProtocolHandler.start()方法,协议处理器又调用了endpoint.start()断点启动方法:

  • 创建启动worker线程池(默认是10个,这里能联系我之前那篇tomcat调优看,这里会去拿最小处理线程数和最大处理线程数的参数)
  • 进行最大连接数maxConnections的限制,最大为8*1024=8192条连接(但是不建议参数设置过大,根据具体机器配置调整)
  • 创建启动一个Poller拉取者单线程,然后在创建启动一个Acceptor接受者单线程

Acceptor一直循环接收端口来的数据(serverSocket.accept,端口开启一个线程在后台一直接收数据),监听8080端口

Tomcat的请求处理

  1. Acceptor一直接收8080的请求,会尝试endpoint.setSocketOptions(socket)
  2. socket封装为socketWrapper,此时poller会先来注册事件 socketWrapper
  3. 创建一个PollerEvent,添加事件队列到SynchronizedQueue<PollerEvent>
  4. Poller一直判断是否有事件.有的话就触发events.poll()
  5. 读取socket的内容并处理processSocket(socketWrapper),poller会拿到worker线程池,把socketWrapper封装到SocketProcessorBase里面,把这个SocketProcessorBase直接扔给线程池
  6. SocketProcessorBase会被线程池的一个线程处理,最终会被ConnectorHandler.process进行处理,交给Http11Processor.process进行处理,最终Http11Processor的service方法最终处理这个request(最终会层层调用到servlet的service方法)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

life or die

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值