Tomcat卷一 ----架构和初始化源码分析

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tomcat卷一 ----架构和初始化源码分析相关的知识,希望对你有一定的参考价值。

Tomcat卷一


本系列主要在于梳理tomcat的整体架构和源码剖析,只挑重点流程进行分析


Tomcat 基础概念扫盲

web 概念

1. 软件架构 

    1. C/S: 客户端/服务器端 ‐‐‐‐‐‐‐‐‐‐‐‐> QQ , 360 .... 

    2. B/S: 浏览器/服务器端 ‐‐‐‐‐‐‐‐‐‐‐‐> 京东, 网易 , 淘宝 

2. 资源分类 

     1. 静态资源: 所有用户访问后,得到的结果都是一样的,称为静态资源。静态资 

源可以直接被浏览器解析。 

    * 如: html,css,javascript,jpg 

    2. 动态资源: 每个用户访问相同资源后,得到的结果可能不一样 , 称为动态资 

源。动态资源被访问后,需要先转换为静态资源,再返回给浏览器,通过浏览器进行解析。 

   * 如:servlet/jsp,php,asp.... 

3. 网络通信三要素 

  1. IP:电子设备(计算机)在网络中的唯一标识。 

  2. 端口:应用程序在计算机中的唯一标识。 0~65536 

  3. 传输协议:规定了数据传输的规则 

 1. 基础协议: 

     1. tcp : 安全协议,三次握手。 速度稍慢 

     2. udp:不安全协议。 速度快 

常见的web服务器

概念

1. 服务器:安装了服务器软件的计算机 

2. 服务器软件:接收用户的请求,处理请求,做出响应 

3. web服务器软件:接收用户的请求,处理请求,做出响应。 
    
     在web服务器软件中,可以部署web项目,让用户通过浏览器来访问这些项目

常见web服务器软件

1). webLogic:oracle公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。 

2). webSphere:IBM公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。 

3). JBOSS:JBOSS公司的,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。 

4). TomcatApache基金组织,中小型的JavaEE服务器,仅仅支持少量的JavaEE规范 servlet/jsp。开源的,免费的。

Tomcat 历史

1) Tomcat 最初由Sun公司的软件架构师 James Duncan Davidson 开发,名称为 “JavaWebServer”。

2) 1999年 ,在 Davidson 的帮助下,该项目于1999年于apache 软件基金会旗下的 JServ 项目合并,并发布第一个版本(3.x), 即是现在的Tomcat,该版本实现了 Servlet2.2 和 JSP 1.1 规范 。

3) 2001年,Tomcat 发布了4.0版本, 作为里程碑式的版本,Tomcat 完全重新设计了 其架构,并实现了 Servlet 2.3 和 JSP1.2规范。


Tomcat 安装

Tomcat官网下载源码包即可,这里使用的是tomcat 8.5.42版本


Tomcat 目录结构


Tomcat 启动停止

启动

双击 bin/startup.bat 文件 

停止

双击 bin/shutdown.bat 文件 

访问

http://localhost:8080


Tomcat源码

Tomcat 8.5源码包下载

运行

1) 解压zip压缩包

2) 进入解压目录,并创建一个目录,命名为home , 并将conf、webapps目录移入 home 目录中

3) 在当前目录下创建一个 pom.xml 文件,引入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>Tomcat8.5</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>

    </dependencies>
</project>

4) 在idea中, 导入该工程


5) 配置idea的启动类, 配置 MainClass , 并配置 VM 参数

由于移动了源码文件的位置,因此需要手动指定新文件的位置,这里用绝对路径,改成自己的绝对路径防止文件找不到

-Dcatalina.home=C:/Users/zdh/Desktop/tomcat/tomcatSrcCode/apache-tomcat-8.5.42-src/home
-Dcatalina.base=C:/Users/zdh/Desktop/tomcat/tomcatSrcCode/apache-tomcat-8.5.42-src/home
-Djava.endorsed.dirs=C:/Users/zdh/Desktop/tomcat/tomcatSrcCode/apache-tomcat-8.5.42-src/home/endorsed
-Djava.io.tmpdir=C:/Users/zdh/Desktop/tomcat/tomcatSrcCode/apache-tomcat-8.5.42-src/home/temp
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=C:/Users/zdh/Desktop/tomcat/tomcatSrcCode/apache-tomcat-8.5.42-src/home/conf/logging.properties


6) 启动主方法, 运行Tomcat , 访问Tomcat 。

说明:如果编译build的时候出现Test测试代码报错,注释该代码即可。本文中的Tomcat源码util.TestCookieFilter类会报错,将其注释即可

运行项目,访问http://localhost:8080,得到结果:

原因是我们直接启动org.apache.catalina.startup.Bootstrap的时候没有加载org.apache.jasper.servlet.JasperInitializer,从而无法编译JSP

解决办法是在tomcat的源码org.apache.catalina.startup.ContextConfig中的configureStart函数中手动将JSP解析器初始化:

protected synchronized void configureStart() 
        // Called from StandardContext.start()

        if (log.isDebugEnabled()) 
            log.debug(sm.getString("contextConfig.start"));
        

        if (log.isDebugEnabled()) 
            log.debug(sm.getString("contextConfig.xmlSettings",
                    context.getName(),
                    Boolean.valueOf(context.getXmlValidation()),
                    Boolean.valueOf(context.getXmlNamespaceAware())));
        

        webConfig();
        
        //这里是需要添加的代码,其余是源码不需要动   
        context.addServletContainerInitializer(new JasperInitializer(), null);
        
        if (!context.getIgnoreAnnotations()) 
            applicationAnnotationsConfig();
        
        if (ok) 
            validateSecurityRoles();
        

        // Configure an authenticator if we need one
        if (ok) 
            authenticatorConfig();
        

        // Dump the contents of this pipeline if requested
        if (log.isDebugEnabled()) 
            log.debug("Pipeline Configuration:");
            Pipeline pipeline = context.getPipeline();
            Valve valves[] = null;
            if (pipeline != null) 
                valves = pipeline.getValves();
            
            if (valves != null) 
                for (int i = 0; i < valves.length; i++) 
                    log.debug("  " + valves[i].getClass().getName());
                
            
            log.debug("======================");
        

        // Make our application available if no problems were encountered
        if (ok) 
            context.setConfigured(true);
         else 
            log.error(sm.getString("contextConfig.unavailable"));
            context.setConfigured(false);
        

    

修改完后,项目再启动,我们再在浏览器访问http://localhost:8080/ ,就可以看到我们所熟悉的经典欢迎页面了


Tomcat 架构

Http工作原理

HTTP协议是浏览器与服务器之间的数据传送协议。

作为应用层协议,HTTP是基于TCP/IP 协议来传递数据的(HTML文件、图片、查询结果等),HTTP协议不涉及数据包 (Packet)传输,主要规定了客户端和服务器之间的通信格式。

从图上你可以看到,这个过程是:

1) 用户通过浏览器进行了一个操作,比如输入网址并回车,或者是点击链接,接着浏览 器获取了这个事件。

2) 浏览器向服务端发出TCP连接请求。

3) 服务程序接受浏览器的连接请求,并经过TCP三次握手建立连接。

4) 浏览器将请求数据打包成一个HTTP协议格式的数据包。

5) 浏览器将该数据包推入网络,数据包经过网络传输,最终达到端服务程序。

6) 服务端程序拿到这个数据包后,同样以HTTP协议格式解包,获取到客户端的意图。

7) 得知客户端意图后进行处理,比如提供静态文件或者调用服务端程序获得动态结果。

8) 服务器将响应结果(可能是HTML或者图片等)按照HTTP协议格式打包。

9) 服务器将响应数据包推入网络,数据包经过网络传输最终达到到浏览器。

10) 浏览器拿到数据包后,以HTTP协议的格式解包,然后解析数据,假设这里的数据是 HTML。

11) 浏览器将HTML文件展示在页面上。

那我们想要探究的Tomcat作为一个HTTP服务器,在这个过程中都做了些什么事情呢?

主 要是接受连接、解析请求数据、处理请求和发送响应这几个步骤。


Tomcat整体架构

Http服务器请求处理

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


1) 图1 , 表示HTTP服务器直接调用具体业务类,它们是紧耦合的。

2) 图2,HTTP服务器不直接调用业务类,而是把请求交给容器来处理,容器通过 Servlet接口调用业务类。

因此Servlet接口和Servlet容器的出现,达到了HTTP服务器与 业务类解耦的目的。

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

Tomcat按照Servlet规范的要求实现了Servlet容器,同时它们也具有HTTP服务器的功 能。

作为Java程序员,如果我们要实现新的业务功能,只需要实现一个Servlet,并把它 注册到Tomcat(Servlet容器)中,剩下的事情就由Tomcat帮我们处理了。


Servlet容器工作流程

为了解耦,HTTP服务器不直接调用Servlet,而是把请求交给Servlet容器来处理,那 Servlet容器又是怎么工作的呢?

当客户请求某个资源时,HTTP服务器会用一个ServletRequest对象把客户的请求信息封 装起来,然后调用Servlet容器的service方法,Servlet容器拿到请求后,根据请求的URL 和Servlet的映射关系,找到相应的Servlet,如果Servlet还没有被加载,就用反射机制创 建这个Servlet,并调用Servlet的init方法来完成初始化,接着调用Servlet的service方法 来处理请求,把ServletResponse对象返回给HTTP服务器,HTTP服务器会把响应发送给 客户端。


Tomcat整体架构

我们知道如果要设计一个系统,首先是要了解需求,我们已经了解了Tomcat要实现两个 核心功能:

1) 处理Socket连接,负责网络字节流与Request和Response对象的转化。

2) 加载和管理Servlet,以及具体处理Request请求。

因此Tomcat设计了两个核心组件连接器(Connector)容器(Container)来分别做这 两件事情。连接器负责对外交流,容器负责内部处理。


tomcat核心组件分析

  • Connector处理 http协议+端口号
  • Host对应域名
  • Context是tomcat应用执行上下文,默认是/,这里设置为lunban
  • Servlet对应资源: 这里分为动态资源和静态资源,静态资源例如后缀是.html,.jpg的直接返回即可,动态资源还需要交给Servlet进行处理后才能返回处理后的静态资源

tomcat核心组件协作过程

科普时间到:

  • 端口号的作用

端口号可以用来标识同一个主机上通信的不同应用程序,端口号+IP地址就可以组成一个套接字,用来标识一个进程

  • 一个进程是否可以bind多个端口号?

可以, 因为一个进程可以打开多个文件描述符,而每个文件描述符都对应一个端口号,所以一个进程可以绑定多个端口号

  • 一个端口号是否可以被多个进程绑定?

不可以, 如果进程先绑定一个端口号,然后在fork一个子进程,这样的话就可以是实现多个进程绑定一个端口号,但是两个不同的进程绑定同一个端口号是不可以的


这里tomcat服务器也可以设置多个端口号,因此对于的连接器也可以有多个

因为域名也可以配置多个,因此站点对应的也可以有多个

这里得出结论1: 连接器和站点是多对多的关系

结论2: 站点和应用上下文是一对多的关系,一个站点下面可以有多个应用上下文,这里应用上下文映射到物理层面对应一个文件夹,然后文件夹下面放着资源

结论3:应用上下文和资源也是一对多的关系,上面也说了,可以把应用上下文本质就是一个文件夹,而所谓的资源就是文件夹下面的.java文件动态资源文件或者.html,.jps等静态资源文件

应用上下文本质就是一堆资源文件集合存放的文件夹


演示

这是一个删减过后的server.xml:

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

<Server port="8005" shutdown="SHUTDOWN">

  <Service name="Catalina">
    <!--    连接器可以存在多个-->
    <Connector port="8080" protocol="HTTP/1.1"/>
    <Connector port="8081" protocol="HTTP/1.1"/>

    <!-- engine可以看做是管理Host的一个容器,这里 defaultHost规定了默认的访问站点     -->
    <Engine name="Catalina" defaultHost="localhost">
      <!-- 站点也可以存在多个,这里对应Host标签里面的name      -->
      <!--这里默认的应用上下文是/,这里appBase是当前站点的根路径 -->
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy

以上是关于Tomcat卷一 ----架构和初始化源码分析的主要内容,如果未能解决你的问题,请参考以下文章

《Java核心技术 卷一》随笔

安卓深度探索(卷一)第十章

java核心技术(卷一)

《荣枯鉴》圆通卷一

安卓深度探索(卷一)第六章

《tcpip详解卷一》:150行代码拉开协议栈实现的篇章