(001)Tomcat核心组件及应用架构详解

1.Web容器是什么?
Web容器是一种服务程序,给处于其中的应用程序组件提供环境,使其直接跟容器中的环境变量交互,不必关注其它系统问题。主要由应用服务器来实现,如Tomcat、JBoss、Weblogic、WebSphere等。

2.Web技术的发展历史
早期的Web应用主要用于浏览新闻等静态页面,HTTP服务器(比如Apache、Nginx)向浏览器返回静态HTML,浏览器负责解析HTML,将结果呈现给用户。
随着互联网的发展,我们已经不满足于仅仅浏览静态页面,还希望通过一些交互操作来获取动态结果,因此也就需要一些扩展机制能够让HTTP服务器调用服务端程序。
于是Sun公司推出了Servlet技术。我们可以把Servlet简单理解为运行在服务端的Java小程序。但是Servlet没有main方法,不能独立运行,因此必须把它部署到Servlet容器中,有容器来实例化并调用Servlet。
而Tomcat就是一个Servlet容器。为了方便使用,他们也具备Http服务器的功能。因此Tomcat就是一个“Http服务器+Servlet容器”,我们也叫它们Web容器。

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

假如浏览器需要从远程HTTP服务器获取一个HTML文件,在这个过程中,浏览器实际上要做两件事情。
1)与服务器建立Socket连接。
2)生成请求数据并通过Scoket发送出去。
如下图大致流程:
在这里插入图片描述

4.HTTP请求响应实例
用户在登陆页面输入用户名和密码,点击登陆后,浏览器发出了这样的 HTTP 请求:
在这里插入图片描述
HTTP请求数据由3部分组成,分别是请求行、请求报头、请求正文。当这个HTTP请求到达Tomcat后,Tomcat会把HTTP请求数据字节流解析成一个Request对象,这个Request对象封装了HTTP所有的请求信息。接着Tomcat会把这个Request对象交给Web应用去处理,处理完后得到一个Response对象,Tomcat会把这个Response对象转换成HTTP格式的响应数据并发送给浏览器。
在这里插入图片描述
HTTP的响应也是由三部分组成,分别是状态行、响应报头、报文主体

5.Cookie和Session
我们知道,HTTP协议有个特点是无状态,请求与请求之间是没有关系的。这样会出现一个很尴尬的问题:Web应用不知道你是谁。因此HTTP协议需要一种技术让请求与请求之间建立联系,并且服务器需要知道这个请求来自哪个用户,于是Cookie技术出现了。
Cookie是HTTP报文的一个请求头,Web应用可以将用户的标识信息或其它一些信息(用户名等)存储在Cookie中。用户经过认证之后,每次HTTP请求报文中都包含Cookie,这样服务器读取这个Cookie请求头就知道用户是谁了。Cookie本质上就是一份存储在用户本地的文件,里面包含了每次请求都需要传递的信息。
由于Cookie以明文的方式存储在本地,而Cookie中往往带有用户信息,这样就造成了非常大的安全隐患。而Session的出现解决了这个问题,Session可以理解为服务端开辟的存储空间,里面保存了用户的状态,用户信息以Session的形式存储在服务端。当用户请求到来时,服务端可以把用户的请求和用户的Session对应起来。那么Session是怎么和请求对应起来的呢?答案是通过Cookie,浏览器在Cookie中填充了一个Session ID之类的字段用来标识请求。
具体工作流程:服务器在创建Session的同时,会为该Session生成一个唯一的Session ID,当浏览器再次发送请求的时候,会将这个Session ID带上,服务器接受到请求之后就会依据Session ID找到相应的Session,找到Session后就可以在Session中获取或添加内容了。而这些内容只保存在服务器中,发到客户端的只有Session ID,这样相对安全,也节省了网络流量,因为不需要在Cookie中存储大量用户信息。
那么Session在何时何地创建呢?当然还是在服务器端程序运行的过程中创建的,不同语言实现的应用程序有不同的创建Session方法。在Java中是Web应用程序在调用HttpServletRequest的getSession方法时,由Web容器(如Tomcat)创建的。
Tomcat的Session管理器提供了多种持久化方案来存储Session,通常会采用高性能的存储方式,比如Redis,并且通过集群的方式部署,防止出现单点故障而导致的宕机,从而提升高可用性。同时Session有过期时间,因此Tomcat会开启后台线程定期的轮询,如果Session过期了就将Session失效。

6.Servlet规范
HTTP服务器怎么知道要调用哪个Java类的哪个方法呢?最直接的做法是在HTTP服务器代码里写一堆if else逻辑判断:如果是A请求就调用X类的M1方法,如果是B请求就调用Y类的M2方法。但是这样做明显有问题,因为HTTP服务器的代码和业务代码耦合在一起了,如果新加一个业务方法还要改HTTP服务器的代码。
那么该如何解决这个问题呢?我们知道,面向接口编程是解决耦合问题的法宝,于是有一伙人就定义了一个接口,各种业务类都必须实现这个接口,这个接口就是Servlet接口,有时我们也把实现了Servlet接口的业务类叫做Servlet。
但是这里还有一个问题,对于特定的请求,HTTP服务器如何知道由哪个Servlet来处理呢?Servlet又是由谁来实例化呢?显然HTTP服务器不适合做这个工作,否则又和业务耦合了。于是,还是那伙人又发明了Servlet容器,Servlet容器用来加载和管理业务类。Http服务器不直接跟业务类打交道,而是把请求交给Servlet处理,Servlet容器会将请求转发到具体的Servlet,如果这个Servlet还没创建,就加载并实例化这个Servlet,然后调用这个Servlet的接口方法。因此Servlet接口其实就是Servlet容器跟具体业务类之间的接口。 如下图所示:
在这里插入图片描述
Servlet接口和Servlet容器这一套规范叫作Servlet规范。Tomcat和Jetty都按照Servlet规范的要求实现了Servlet容器,同时它们也具有HTTP服务器功能。作为Javav程序员,我们如果要实现新的业务功能,只需要实现一个Servlet,并把它注册到Tomcat(Servlet容器)中,剩下的事情就交给Tomcat帮我们处理。
Servlet接口定义了下面5个方法:

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

其中最重要的就是service方法,具体业务类在这个方法里实现处理逻辑。这个方法有两个参数:ServletRequest和ServletResponse。ServletRequest用来封装请求信息,ServletResponse用来封装响应信息,因此本质上这两个类是对通信协议的封装。

Http协议中的请求和响应就是对应了HttpServletRequest和HttpServletResponse这两个类。你可以通过HttpServletRequest来获取所有请求相关的信息,包括请求路径、Cookie、HTTP头、请求参数等。此外,我们还可以通过HttpServletRequest来创建和获取Session。而HttpServletResponse用来封装HTTP响应。

你可以看到接口中还有两个跟生命周期有关的方法init和destory,这是一个比较贴心的设计,Servlet容器在加载Servlet类的时候会调用init方法,在卸载的时候会调用destory方法。我们可能会在init方法里初始化一些资源,并在destory方法里释放这些资源,比如Spring MVC中的DispatcherServlet就是在init方法里创建了自己的Spring容器。

你还会注意到ServletConfig这个类,ServletConfig的作用就是封装Servlet的初始化参数。你可以在web.xml给Servlet配置参数,并在程序里通过getServletConfig方法拿到这些参数。

我们知道有接口一般就有抽象类,抽象类用来实现接口和封装通用的逻辑,因此Servlet规范提供了GenericServlet抽象类,我们可以通过扩展它来实现Servlet。虽然Servlet规范并不在乎通信协议是什么,但是大多数的Servlet都是在HTTP环境中处理的,因此Servlet规范还提供了HttpServlet来集成GenericServlet,并且加入了HTTP特性。这样我们通过继承HttpServlet来现实自己的Servlet,只需要重写两个方法:doGet和doPost。

7.Servlet容器
当客户端请求某个资源时,HTTP服务器会用一个ServletRequest对象把客户端的请求信息封装起来,然后调用Servlet容器的service方法,Servlet容器拿到请求后,根据请求的URL和Servlet的映射关系,找到相应的Servlet,如果Servlet还没被加载,就用反射机制创建这个Servlet,并调用Servlet的init方法来完成初始化,接着调用Servlet的service方法来处理请求,把ServletResponse对象返回给HTTP服务器,HTTP服务器会把响应发送给客户端。
在这里插入图片描述
8.Web应用
Servlet容器会实例化和调用Servlet,那么Servlet是怎么注册到Servlet容器中的呢?一般来说,我们是以Web应用程序的方式部署Servlet的,而根据Servlet规范,Web应用程序有一定的目录结构,在这个目录下分别放置了Servlet的类文件、配置文件以及静态资源,Servlet容器通过读取配置文件,就能找到并加载Servlet。Web应用的目录结构大致是下面这样的:
在这里插入图片描述
Servlet规范里定义了ServletContext这个接口来对应一个Web应用。Web应用部署好后,Servlet容器在启动时会加载Web应用,并为每个Web应用创建唯一的ServletContext对象。你可以把ServletContext看成是一个全局对象,一个Web应用可能有多个Servlet,这些Servlet可以通过全局的ServletContext来共享数据,这些数据包括Web应用的初始化参数、Web应用目录的文件资源等。由于ServletContext持有所有Servlet实例,你可以通过它来实现Servlet请求的转发。

9.扩展机制
引入Servlet规范后,你不需要关心Scoket网络通信、不需要关心HTTP协议,也不需要关心你的业务类是如何被实例化和调用的,因为这些都被Servlet规范标准化了,你只要关心怎么实现你的业务逻辑。这对于程序员来说是件好事,但也有不方便的一面。所谓规范就是说大家都要遵守,就会千篇一律,但是如果这个规范不能满足你的业务的个性化需求,就会有问题,因此设计一个规范或者一个中间件,要充分考虑到可扩展性。Servlet规范提供了两种扩展机制:Filter和Listener

Filter是过滤器,这个接口允许你对请求和响应做一些统一的定制化处理,比如你可以根据请求的频率来限制访问,或者根据国家地区的不同修改响应内容。过滤器的工作原理是这样的:Web应用部署完成后,Servlet容器需要实例化Filter并把Filter链接成一个FilterChain。当请求进来时,获取第一个Filter并调用doFilter方法,doFilter方法负责调用这个FilterChain中的下一个Filter。

Listener时监听器,这是另一种扩展机制。当Web应用在Servlet容器运行时,Servlet容器内部会不断发生各种事件,如Web应用的启动和停止、用户请求到达等。Servlet容器提供了一些默认的监听器来监听这些事件,当事件发生时,Servlet容器会负责调用监听器的方法。当然,你可以定义自己的监听器去监听你感兴趣的事件,来监听ServletContext的启动事件,目的是当Servlet容器启动时,创建并初始化全局的Spring容器。

10.Tomcat目录说明
在这里插入图片描述
在这里插入图片描述
打开Tomcat的日志目录,也就是Tomcat安装目录下的logs目录。Tomcat的日志信息分为二类:
1)运行日志:它主要记录运行过程中的一些信息,尤其是一些异常错误日志信息。
2)访问日志:它记录访问的时间、IP地址、访问路径等相关信息。

  • catalina.***.log 主要记录Tomcat启动过程的信息,在这个文件可以看到启动的JVM参数以及操作系统等日志信息。
  • catalina.out是Tomcat的标准输出(stdout)和标准错误(stderr),这是在Tomcat启动脚本指定的,如果没有修改的话Stdout和stderr会重定向到这里。
  • localhost.**.log主要记录Web应用在初始化过程中遇到未处理的异常,会被Tomcat捕获而输出这个日志文件。
  • localhost_access_log.**.txt存放访问tomcat的请求日志,包括IP地址以及请求的路径、时间、请求协议以及状态码等信息。
  • manager.***.log/host-manager.***.log存放Tomcat自带的Manager项目的日志信息。

11.Tomcat架构说明
Tomcat是一个基于JAVA的WEB容器,其实现类Java EE中的Servlet和jsp规范,与Nginx apache服务器不同在于一般用于动态请求处理。在架构设计上采用面向组件的方式设计。即整体功能是通过组件的方式拼装完成。另外每个组件都可以被替换以保证灵活性。

12.Tomcat各组件以及关系
在这里插入图片描述

  • Server和Service

  • Connector连接器
    HTTP1.1
    SSL https
    AJP( Apache JServ Protocol)apache私有协议,用于apache反向代理tomcat.

  • Container
    Engine引擎 catalina
    Host虚拟机 基于域名分发请求
    Context 隔离各个Web应用。 每个Context的ClassLoader都是独立的

  • Component
    Manager 管理器
    logger 日志管理器
    loader 载入器
    pipeline 管道
    valve 管道中的阀

13.Tomcat Server.xml配置详解

 <!----------------------------------------------------------------------------------------------->
<!-- 启动Server 在端口8005处等待关闭命令 如果接受到"SHUTDOWN"字符串则关闭服务器 -->
<Server port="8005" shutdown="SHUTDOWN" debug="0">
<!-- Listener ??? 目前没有看到这里 -->
<Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" debug="0"/>
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" debug="0"/>
<!-- Global JNDI resources ??? 目前没有看到这里,先略去 -->
<GlobalNamingResources>
 ... ... ... ...
</GlobalNamingResources>
<!-- Tomcat的Standalone Service Service是一组Connector的集合 它们共用一个Engine来处理所有Connector收到的请求 -->
<Service name="Tomcat-Standalone">
<!-- Coyote HTTP/1.1 Connector className : 该Connector的实现类是org.apache.coyote.tomcat4.CoyoteConnector port :
在端口号8080处侦听来自客户browser的HTTP1.1请求 minProcessors : 该Connector先创建5个线程等待客户请求,
每个请求由一个线程负责 maxProcessors : 当现有的线程不够服务客户请求时,若线程总数不足75个,则创建新线程来处理请求
acceptCount : 当现有线程已经达到最大数75时,为客户请求排队 当队列中请求数超过100时,后来的请求返回Connection refused
错误 redirectport : 当客户请求是https时,把该请求转发到端口8443去 其它属性略 -->
<Connector className="org.apache.coyote.tomcat4.CoyoteConnector"
 port="8080"
 minProcessors="5" maxProcessors="75" acceptCount="100"
 enableLookups="true"
 redirectPort="8443"
 debug="0"
 connectionTimeout="20000"
 useURIValidationHack="false"
 disableUploadTimeout="true" />
<!-- Engine用来处理Connector收到的Http请求 它将匹配请求和自己的虚拟主机,
并把请求转交给对应的Host来处理默认虚拟主机是localhost -->
<Engine name="Standalone" defaultHost="localhost" debug="0">
<!-- 日志类,目前没有看到,略去先 -->
<Logger className="org.apache.catalina.logger.FileLogger" .../>
<!-- Realm,目前没有看到,略去先 -->
<Realm className="org.apache.catalina.realm.UserDatabaseRealm" .../>
<!-- 虚拟主机localhost appBase : 该虚拟主机的根目录是webapps/ 它将匹配请求和
自己的Context的路径,并把请求转交给对应的Context来处理 -->
<Host name="localhost" debug="0" appBase="webapps" unpackWARs="true" autoDeploy="true">
<!-- 日志类,目前没有看到,略去先 -->
<Logger className="org.apache.catalina.logger.FileLogger" .../>
<!-- Context,对应于一个Web App path : 该Context的路径名是"",故该Context是该Host的
默认Context docBase : 该Context的根目录是webapps/mycontext/ -->
<Context path="" docBase="mycontext" debug="0"/>
<!-- 另外一个Context,路径名是/wsota -->
<Context path="/wsota" docBase="wsotaProject" debug="0"/>
</Host>
</Engine>
</Service>
</Server>
 
<!----------------------------------------------------------------------------------------------->
  • Server root元素,最顶级的配置
    在这里插入图片描述
    port:执行关闭命令的端口号
    shutdown:关闭命令
    演示shundown的用法:(基于telnet执行SHUTDOWN命令即可关闭(必须大写))
    telnet 127.0.0.1 8005 SHUTDOWN

  • service(服务) 将多个connector与一个Engine组合成一个服务,可以配置多个服务。
    在这里插入图片描述

  • Container
    Container是一个接口,定义了下属各种容器,尤其是Wrapper、Host、Engine、Context。
    在这里插入图片描述

  • Engine(引擎) 负责处理来自相关联的Service的所有请求,处理后,将结果返回给service,而Connector是作为service与engine的中间媒介出现的。一个engine下可以配置一个默认主机,每个虚拟主机都有一个域名。当engine获得一个请求时,它把该请求匹配到虚拟主机(host)上,然后把请求交给主机来处理。engine有一个默认主机,当请求无法匹配到任何一个虚拟主机时,将交给默认host来处理。engine以线程的方式启动host。
    在这里插入图片描述

主要属性如下: 在这里插入图片描述

1)name:引擎名称
2)default   默认host
  • Host(虚拟机)
    代表一个虚拟主机,每个虚拟主机和某个网络域名相匹配。每个虚拟主机下都可以部署一个或多个Web应用,每个web应用对应于一个context,有一个context path。当Host获得一个请求时,将把该请求匹配到某个Context上,然后把该请求交给Context来处理,匹配的方法是“最长匹配”,所以一个path==””的context将成为该Host默认的。context所有无法和其它context的路径名匹配的请求都将最终和该默认的context匹配。
    在这里插入图片描述

  • Context(应用上下文)
    一个context对应一个Web应用,一个Web应用将由一个或多个servlet组成。context在创建的时候将根据配置文件$CATALINA_HOME/conf/web.xml和 $WEBAPP_HOME/WEB-INF/web.xml载入servlet类。将context获得请求时,将在自己的映射表中寻找匹配的servlet类,如果找到则执行该类,获得请求的回应,并返回。

  • Wrapper
    Wrapper代表一个servlet,它负责管理一个servlet,包括servlet的装载、初始化、执行以及资源回收。Wrapper时最底层的容器,它没有子容器了,所以调用它的addChild将会报错。Wrapper的实现类是StandardWrapper,StandardWrapper还实现了拥有一个servlet初始化信息的ServletConfig,由此StandardWrapper将直接和servlet的各种信息打交道。

  • Connector(连接器)
    Connector将在某个指定的端口上来监听客户的请求,把从socket传递过来的数据封装成Request,然后传递给Engine来处理,并从Engine处获得响应并返回给客户。
    Tomcat通常会用到两种Connector:
    1)Http Connector 在端口8080处侦听来自客户browser的http请求。 AJP Connector
    2)在端口8009处侦听来自其它WebServer(Apache)的servlet/jsp代理请求。
    常用的一些属性介绍:
    1)port 指定服务器端要创建的端口号
    2)protocol 监听的协议,默认是http/1.1
    3)maxThreads 最大可以创建的处理请求的线程数
    4)enableLookups 如果为true,则可以通过调用request.getRemoteHost() 进行DNS查询来得到远程客户端的实际主机名,若为false则不进行DNS查询,而是返回其IP地址。
    5)redirectPort 指定服务器正在处理http请求时收到一个SSL传输请求后重定向的端口号。
    6)acceptCount 指定当前所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数,超过这个数的请求将不予处理。
    7)connectionTimeout 指定超时的时间(以毫秒为单位)
    8)SSLEnabled 是否开启ssl验证,在Https访问时需要开启。 生成证书:
    keytool ­genkey ­v ­alias testKey ­keyalg RSA ­validity 3650 ­ keystore D:\test.keystore

一个比较完整的配置DEMO:(如下图)

<Connector port="8860" protocol="org.apache.coyote.http11.Http11NioProtocol" 
connectionTimeout="20000" 
 redirectPort="8862" 
 URIEncoding="UTF‐8" 
 useBodyEncodingForURI="true" 
 compression="on" 
 compressionMinSize="2048" 
  compressableMimeType="text/html,text/xml,text/plain,text/javascript,text/ css,application/x‐json,application/json,application/x‐javascript" 8 maxThreads="1024" minSpareThreads="200" 9 acceptCount="800"  enableLookups="false"
  />
  • Valve(阀门) 可以理解成过滤器,具体配置要基于具体的Valve接口的子类。
  • Lifecycle
    在tomcat容器相关的好多组件都实现了Lifecycle接口,当tomcat启动时,其依赖的下层组件会全部进行初始化。并且可以对每个组件生命周期中的事件添加监听器。
    例如当服务器启动的时候,tomcat需要去调用servlet的init方法和初始化容器等一些列操作,而停止的时候也需要调用servlet的destory方法。这些都是通过org.apache.catalina.Lifecycle接口来实现的。由这个类来制定各个组件生命周期的规范。
    在这里插入图片描述
  • LifecycleListener
    在Lifecycle的介绍中提到,Lifecycle会对每个组件生命周期中的事件添加监听器,也就是addLifecycleListener(LifecycleListener listener)方法,而LifecycleListener就是上面提到的监听器。
  • LifecycleEvent
    顾名思义,就是当有监听事件发生的时候,LifecycleEvent会存储时间类型和数据。
public final class LifecycleEvent extends EventObject {
    private static final long serialVersionUID = 1L;
    private final Object data;
    private final String type;

    public LifecycleEvent(Lifecycle lifecycle, String type, Object data) {
        super(lifecycle);
        this.type = type;
        this.data = data;
    }

    public Object getData() {
        return this.data;
    }

    public Lifecycle getLifecycle() {
        return (Lifecycle)this.getSource();
    }

    public String getType() {
        return this.type;
    }
}

14.Tomcat整体架构
我们想要了解一个框架,首先要了解它是干什么的,Tomcat我们都知道是用来处理连接过来的socket请求的。那么tomcat就会有两个功能:
1)对外处理连接,将收到的字节流转换为自己想要的Request和Response对象。
2)对内处理servlet,将对应的Request请求分发到相应的servlet中。

那么我们整体的骨架就出来了,Tomcat其实就分为两个大部分,一部分是连接器(Connector)处理对外连接和容器(Container)管理对内的Servlet。 大致关系如下图:
在这里插入图片描述
最外层的大框架就是代表一个Tomcat服务,一个Tomcat服务可以对应多个Service。每个Service都有连接器和容器。

15.Tomcat各个组件对应的实现类
在这里插入图片描述

16.Tomcat整体启动流程(用一个简单时序图说明下流程)
在这里插入图片描述

由上图我们可以看到从Bootstrap类的main方法开始,tomcat会以链的方式逐级调用各个模块的init()方法进行初始化,待各个模块都初始化后,又会逐级调用各个模块的start()方法启动各个模块。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值