写在前面

实习的公司做的前后端不分离的MVC模式的项目,所以Tomcat需要自己配,之前都是用springboot,对于Tomcat了解甚少。所以刷这本书,熟悉熟悉,整理笔记。加油生活。更新中.....          2020.7.5

笔记分三部分:

  • ​​第一部分:Tomcat面试题笔记​​
  • 第二部分:《Tomcat内核设计剖析》读书笔记
  • 第三部分:Tomcat运维笔记

​第一部分:面试题笔记​

第二部分:《Tomcat内核设计剖析》读书笔记

第1章Web服务器机制

.1 通信协议

关于网络的相关知识,推荐《图解HTTP》和《图解TCP/IP》这两本书,可以先看HTTP 的之后在看TCP/IP.

1.1.1 HТТP/HTTPS.

HTTP是一个应用层协议,它由请求和响应组成,是一个标准的B/S模型。同时,它也是一个无状态的协议,即同一个客户端上,此次请求与上一次请求是没有对应关系的。

HTTPS简单地说就是HTTP的安全版,只是在HTTP增加了一个SSL或TLS协议层。

在TCP协议上加一层SSL或TLS协议,就构成HTTPS协议了。SSL/TLS协议提供了加解密的机制,所以它比HTTP明文传输更安全

一般HTTP的端口号为80,而HTTPS的端口号为443。简单地说, SSL/TLS协议层主要的职责就是借助下层协议的信道安全地协商出一份加密密钥,并且用此密钥来加密HTTP请求响应报文。它解决了以下三个安全性方面的

  • 提供验证服务,验证本次会话实体身份的合法性。
  • 提供加密服务,强加密机制能保证通信过程中的消息不会被破译。
  • 提供防篡改服务,利用Hash算法对消息进行签名,通过验证签名保证通信内容不被

加密解密算法与Hash算法:

  1. 对称加密。密钥只有一个,加密、解密都是这个密码,加解密速度快,典型的对称加密算法有DES、AES、RC4等。,
  2. 非对称加密。密钥成对出现,分别为公钥与私钥,从公钥无法推知私钥,反之,从私钥也不能推知公钥。加密、解密使用不同的密钥,公钥加密需要私钥解密,反之,私钥加密需要公钥解密。非对称加密速度较慢,典型的非对称加密算法有RSA, DSA, DSS等。
  3.  Hash算法,这是一种不可逆的算法,它常用于验证数据的完整性。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_套接字

1.1.2 HTTP请求/响应模型.

HTTP协议永远都由客户端发起请求,由服务器进行响应并发送回响交报文。服务器是无法将消息推送到客户端的。

1.1.3 解析HTTP报文

 

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_套接字_02

1.2 套接字通信

套接字通信是应用层与TCPIP协议族通信的中间抽象层,它是一组接口。应用层通过调用这些接口发送和接收数据

一般这种抽象层由操作系统提供或者由JVM自己实现使用套接字通信可以简单地实现应用程序在网络上的通信

TCP/IP协议族中有两种套接字类型,分别是流套接字和数据报套接字,分别对应TCP协议和UDP协议。

一个TCP/IP套接字由一个互联网地址、一个协议及一个端口号唯一确定。

套接字抽象层位于传输层与应用层之间它类似于设计模式中的门面模式,用户没必要知道和处理复杂的TCPIP协议族业务逻辑的细节,它把这些复杂的处理过程都隐藏在套接字接口下面,帮助用户解析组织TCP/IP协议族报文数据,以符合TCPIP协议族,这用户只要简单调用接口即可实现数据的通信操作。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_客户端_03

1.2.1 单播通信

单播通信是网络节点之间通信方式的一种。单个网络节点与单个网络节点之间的通信就称为单播通信。它是一种一对一的模式。

1.2.2 组播通信

所以组播通信其实是为了弥补单播通信在某些使用场景的局限性,它是一种一对多的传播方式。假如某个主机结点想接收相关的信息,它只需要向路由器或交换机申请加入某组即可,路由器或交换机在接收到相关信息后就会负责向组内所有成员发送信息。组播通信有以下特点:

  1. 节省网络资源:
  2. 有针对性地向组内成员传播:
  3. 可以在互联网上进行传播协议,会导致数据不可靠

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_学习_04

  • 组播通信中最重要的内容是如何维护路由器与主机之间的关系,其主要通过IGMP协议进行维护。它主要维护不同路由器与不同主机之间的成员关系,具体的维护方式比较复杂IGMP协议主要负责组成员的加入和退出、组内成员查询等功能
  • 因为组播通信相当于把主机与主机之间的通信压力转嫁到了路由器上面,所以要得到路由及网络的支持才能进行组播通信。
  • 另外,你的主机必须支持组播通信,在TCPIP层面支持组播发送与接收在IP层面需要一个组播地址以指定组播,
  • 它称为D类地址,范围是224.0.0~239.255.255.255这些地址根据范围大致分为局域网地址和因特网地址, 224.0.0.0-244.0.255用于局域网, 224.0.1.0~238.255.255.255用于因特网。
  • Tomcat默认的组播地址为228.0.0.4,而Tomcat为何会涉及组播通信则要归到集群的概念,因为集群涉及内存的共享问题,所以需要使用组播通信进行数据同步
  • 单播通信模式中有服务器端和客户端之分,而组播通信模式与单播通信模式不同,每个端都是以路由器或交换机作为中转广播站,任意一端向路由器或交换机发送消息,路由器或交换机负责发送给其他节点,每个节点都是等同的。

1.2.3广播通信

它与组播通信又有不同的地方。

  1. 广播通信的重点在于广,它向路由器连接的所有主机都发送消息而不管主机想不想要,虽然浪费了网络资源,但它可以不用维护路由器与主机之间的成员关系。广播通信只能在局域网内传播
  2. 组播通信的重点在于组,它只会向加入了组的所有成员发送消息,具有针对性强、不浪费网络资源的特点。组播通信能在公网内传播

1.3 服务器模型

服务器端对IO的处理模型。

1.3.1 单线程阻塞IO模型

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_套接字_05

这种模型特点在于单线程和阻塞IO:

  • 单线程即服务器端只有一个线程处理客户端的所有请求,客户端连接与服务器端的处理线程比是n:1,它无法同时处理多个连接,只能串行处理连接。
  • 阻塞IO是指服务器在读写数据时是阻塞的,读取客户端数据时要等待客户端发送数据并且把操作系统内核复制到用户进程中,这时才解除阻塞状态。写数据回客户端时要等待用户进程将数据写入内核并发送到客户端后才解除阻塞状态。这种阻塞给网络编程带来了一个问题,服务器必须要等到客户端成功接收才能继续往下处理另外一个客户端的请求,在此期间线程将无法响应任何客户端请求。

该模型的特点:

  1. 最简单的服务器模型,整个运行过程都只有一个线程,只能支持同时处理一个客户端的请求(如果有多个客户端访问,就必须排队等待),
  2. 服务器系统资源消耗较小
  3. 并发能力低,容错能力差。

1.3.2 多线程阻塞IO模型

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_java_06

多线程阻塞IO模型的特点:

  • 支持对多个客户端并发响应,处理能力得到大幅提高,有较大的并发量,
  • 服务器系统资源消耗量较大,而且多线程之间会产生线程切换成本,同时拥有较复杂的结构。

1.3.3 单线程非阻塞IO模型

在调用读取或写入接口后立即返回,而不会进入阻塞状态。

非阻塞情况下套接字事件的检测机制,一般会有如下三种检测方式

  • (1)应用程序遍历套接字的事件检测
  • 当多个客户端向服务器请求时,服务器端会保存一个套接字连接列表中,应用层单个线程对套接字列表轮询尝试读取或写入。对于读写操作,如果成功读写到若干数据,则对读写到的数据进行处理;如果读写失败,则下一个循环再继续尝试。这很好地利用了阻塞的时间,处理能力得到提升。但这种模型需要在应用程序中遍历所有的套接字列表,同时需要处理数据的拼接,连接空闲时可能也会占用较多CPU资源,不适合实际使用。对此改进的方法是使用事件驱动的非阻塞方式。
  • (2)内核遍历套接字的事件检测
  • 将套接字的遍历工作交给了操作系统内核,对套接字遍历的结果组织成一系列的事件列表并返回应用层处理。对于应用层,它们需要处理的对象就是这些事件,这就是其中一种事件驱动的非阻塞方式的实现。然而,它需要将所有连接的可读写事件列表传到应用层,假如套接字连接数量变大,列表从内核复制到应用层也是不小的开销。另外,当活跃连接较少时,内核与应用层之间存在很多无效的数据副本,因为它将活跃和不活跃的连接状态都复制到应用层中。
  • (3)内核基于回调的事件检测
  • 内核中的套接字都对应一个回调函数,当客户端往套接字发送数据时,内核从网卡接收数据后就会调用回调函数,在回调函数中维护事件列表,应用层获取此事件列表即可得到所有感兴趣的事件。内核基于回调的事件检测方式有两种。
  • 第一种是用可读列表readList和可写列表writeList标记读写事件,套接字的数量与readList和writeList两个列表的长度一样, readList第一个元素标为1则表示套接字1可读,同理, writeList第二个元素标为1则表示套接字2可写。多客户端连接服务器端,当客户端发送数据过来时,内核从网卡复制数据成功后调用回调函数将readList第一个元素置为1,应用层发送请求读、写事件列表,返回内核包含了事件标识的readList和writeList事件列表,进而分表遍历读事件列表readList和写事件列表writeL.ist,对置为1的元素对应的套接字进行读或写操作。
  • 第二种应用层告诉内核每个套接字感兴趣的事件。,当客户端发送数据过来时,对应会有一个回调函数,内核从网卡复制数据成功后即调回调函数将套接字1作为可读事件eventl加入到事件列表。同样地,内核发现网卡可写时就将套接字2作为可写事件event2添加到,事件列表中。最后,应用层向内核请求读、写事件列表,内核将包含了eventl和event2的事件列表返回应用层,应用层通过遍历事件列表得知套接字1有数据待读取,于是进行读操作,而套接字2则可以写入数据。
  • 两种方式由操作系统内核维护客户端的所有连接并通过回调函数不断更新事件列表,而应用层线程只要遍历这些事件列表即可知道可读取或可写入的连接,进而对这些连接进行读写操作,极大提高了检测效率, 自然处理能力也更强。
  • 对于Java来说,非阻塞IO的实现完全是基于操作系统内核的非阻塞IO,它将操作系统,的非阻塞IO的差异屏蔽并提供统一的API,让我们不必关心操作系统。JDK会帮我们选择非阻塞IO的实现方式,例如对于Linux系统,在支持epoll的情况下JDK会优先选择用epoll实现Java的非阻塞IO。这种非阻塞方式的事件检测机制就是效率最高的“内核基于回调的事件检测”中的第二种方式。

1.3.4 多线程非阻塞IO模型

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_套接字_07

Reactor模式

最经典的多线程非阻塞IO模型方式是Reactor模式。

首先看单线程下的Reactor, Reactor将服务器端的整个处理过程分成若干个事件,例如分为接收事件、读事件、写事件、执行事件等。

Reactor通过事件检测机制将这些事件分发给不同处理器去处理,这些处理器包括:

  1. 接收连接的accept处理器
  2. 读数据的read处理器
  3. 写数据的write处理器
  4. 执行逻辑的process处理器。

在整个过程中只要有待处理的事件存在,即可以让Reactor线程不断往下执行,而不会阻塞在某处,所以处理效率很高。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_客户端_08

基于单线程Reactor模型,根据实际使用场景,把它改进成多线程模式。常见的有两种方式:

  1. 一种是在耗时的process处理器中引入多线程,如使用线程池;
  2. 直接使用多个Reactor实例,每个Reactor实例对应一个线程所有客户端的连接接受工作统一由一个accept处理器构成,appept会将接受的客户端连接均分配改所有的Reactor实例。

第2章Serverlt规范

2.1 Servelet接口

Servlet规范的核心接口即是Servlet接口,它是所有Servlet类必须实现的接口。在JavaServelt API中已经提供了两个抽象类方便开发者实现Servlet类,分别是GenericServlet和HttpServlet, 

  • GenericServlet定义了一个通用的、协议无关的Servlet,
  • HttpServlet则定义了HTTP的Servlet,

Servlet接口的核心方法为service方法,它是处理客户端请求的方法,客户端发起的请求会被路由到对应的Servlet对象上。前面说到的HttpServlet类的service方法把对HTTP协议的GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE等请求转发到各自的处理方法中,即doGet, doPost, doPut, doDelete, doHead, doOptions, doTrace等方法。

一般来说,在Servlet容器中,每个Servlet类只能对应一个Servlet对象,所有请求都由同一个Servlet对象处理,但如果Servlet实现了SingleThreadModel接口则可能会在Web容器中存在多个Servlet对象对于web容器来说,实现了SingleThreadModel接口意味着一个Servlet对象对应着一个线程,所以此时Servlet的成员变量不存在线程安全问题

Servlet的生命周期主要包括加载实例化、初始化、处理客户端请求、销毁。

  • 加载实例化主要由Web容器完成,
  • 而其他三个阶段则对应Servlet的init, service和destroy方法。

Servlet对象被创建后需要对其进行初始化操作,初始化工作可以放在以ServletConfig类型为参数的init方法中, ServletConfig为web.xml配置文件中配置的对应的初始化参数, 由Web容器完成web.xml配置读取并封装成ServletConfig对象

当Servlet初始化完成后,开始接受客户端的请求,这些请求被封装成ServletRequest类型的请求对象和ServletResponse类型的响应对象,

2.2 ServletRequest接口

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_客户端_09

2.3 ServletContext接口

ServletContext接口定义了运行所有Servlet的Web应用的视图。其提供的内容包括以下几个部分。

  1. 某个Web应用的Servlet全局存储空间,某Web应用对应的所有Servlet共有的各种资源和功能的访问。
  2. 获取Web应用的部署描述配置文件的方法,例如getlnitParameter和getInitParameterNames.
  3. 添加Servlet到ServletContext里面的方法,例如addServlet.添加Filter (过滤器)到ServletContext里面的方法,例如addFilter.添加Listener (监听器)到ServletContext里面的方法,例如addListener.
  4. 全局的属性保存和获取功能,例如setAttribute, getAttribute, getAttributeNames和removeAttribute等。

2.4 ServletResponse接口

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_套接字_10

2.5 Filter接口

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_套接字_11

2.6会话

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_java_12

2.7注解 

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_tomcat_13

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_tomcat_14

2.8 可插拔性


为了给web开发人员提供更好的可插拔性和更少的配置,可以在一个库类或框架jar包的META-INF目录中指定Web Fragment,

即web-fragment.xml配置文件,它可以看成Web的逻辑分区, web-fragment.xml与web.xml包含的元素基本上都相同。

部署期间, Web容器会扫描WEB-INF/lib目录下jar包的META-INF/web-fragment.xml文件,并根据配置文件生成对应的组件。

一个Web应用可能会有一个web.xml和若干个web-fragment.xml文件, Web容器加载时会涉及顺序问题。有两种方式定义它们加载的顺序:

  • 绝对顺序, web.xml中的<absolute-ordering>元素用于描述加载资源的顺序;
  • 相对顺序, web-fragment.xml中的<ordering>元素用于描述web-fragment.xml之间的顺序。

2.9 请求分发器

请求分发器负责把请求转发给另外一个Servlet处理,或在响应中包含另外一个Servlet的输出

RequestDispatcher接口提供了此实现机制。用户可以通过ServletContext的getRequestDispatcher方法getNamedDispatcher方法分别以路径或Servlet名称作为参数获取对应Servlet的RequestDispatcher.

请求分发器有include和forward两个方法。

  1. include方法是将目标Servlet包含到当前的Servlet中,主控制权在当前Servlet上。
  2. forward方法是将当前Servlet的请求转移到目标Servlet上,主控权在目标Servlet上,当前Servlet的执行终止。

2.10 Web应用

Web应用和ServletContext接口对象是一对一的关系, ServletContext对象提供了一个Servlet和它的应用程序视图。

Web应用可能包括Servlet, JSP、工具类、静态文件、客户端JavaApplet等。

Web应用结构包括

  • WEB-INF/web.xml文件
  • WEB-INF/ib/目录下存放的所有jar包
  • WEB-INF/classes/目录中存放的所有类
  • META-INF目录存放的项目的一些信息
  • 以及其他根据具体目录存放的资源。

一般WEB-INF目录下的文件都不能由容器直接提供给客户端访问,但WEB-INF目录中的内容可以通过Servlet代码调用ServletContext的getResource和getResourceAsStream方法来访问,并可使用RequestDispatcher调用公开这些内容。

Web容器用于加载WAR文件中Servlet的类加载器必须提供getResource方法,以加载WAR文件的JAR包中包含的任何资源。容器不允许Web应用程序覆盖或访问容器的实现类。一个类加载器的实现必须保证部署到容器的每个Web应用,在调用Thread.currentThread.getContextClassLoader() 时返回一个规定的ClassLoader实例。部署的每个Web应用程序的ClassLoader实例必须是一个单独的实例。

服务器应该能在不重启web容器的情况下更新一个Web应用程序,而更新web应用程序时Web容器应该提供可靠的方法保存这些Web应用的会话。如果调用response的sendError方法或如果Servlet产生一个异常或把错误传播给容器,容器要按照Web应用部署描述文件中定义的错误页面列表,根据状态码或异常试图返回一个匹配的错误页面。如果Web应用部署描述文件的error-page元素没有包含exception-type或Tor-code子元素,则错误页面使用默认的错误页面。

欢迎页:

Web应用的部署描述符中可以配置欢迎文件列表。当一个Web的请求URI没有映射到一个Web资源时,可以从欢迎文件列表中按顺序匹配适合的资源返回给客户端,如欢迎页为index.html,则http:/ocalhost:8080/webapp请求实际变为http:/ocalhost:8080/webapp/index.html。如果找不到对应的欢迎页,则返回404响应。

当一个Web应用程序部署到容器中时,在Web应用程序开始处理客户端请求之前,必须按照下述步骤顺序执行:

  • ①实例化部署描述文件中<listener>元素标识的每个事件监听器的一个实例。
  • ②对于已实例化且实现了ServletContextListener接口的监听器实例,调用contextinitialized0)方法。
  • ③实例化部署描述文件中<filter>元素标识的每个过滤器的一个实例,并调用每个过滤器实例的init()方法。
  • ④根据load-on-startup元素值定义的顺序,包含<load-on-startup>元素的<servlet>元素为每个Servlet实例化一个实例,并调用每个Servlet实例的init()方法。对于不包含任何Servlet. Filter或Listener的Web应用,或使用注解声明的Web应用,可以不需要web.xml部署描述符

2.11 Servlet映射

对于请求的URL, Web容器根据最长的上下文路径匹配请求URL,然后匹配Servlet,

  1. Servlet的路径是从整个请求URL中减去上下文和路径参数。匹配规则如下:
  2.  Web容器尝试匹配一个精确的Servlet路径,如果匹配成功,则选择该Servlet.
  3.  Web容器递归尝试匹配最长的路径前缀。
  4. 如果URL最后包含扩展名,例如jsp, Web容器将试图匹配一个专门用于处理此扩展名的Servlet如果前三个规则都不匹配,则匹配一个默认的Servlet。

2.12部署描述文件

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_java_15

第3章Tomcat的启动与关闭

3.1 Tomcat的批处理

Tomcat的启动和关闭批处理脚本放在安装目录的bin子目录里,其中不仅包含了Windows系统的bat文件,同时还包含了UNIXLinux的shell文件。

3.1.1 startup.bat

Tomcat的启动和关闭批处理脚本放在安装目录的bin子目录里,包含了Windows系统的bat文件,同时还包含了UNIX/Linux的shell文件。

startup.bat是一个启动批处理脚本,它的主要功能就是找到另一个批处理脚本catalina.bat,并且执行catalina.bat,所以,将整个startup.bat的内容分成两部分讲解

  1. 设置CATALINA_HOME 的环境变量。
  2. Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_tomcat_16


  3. 接收参数,在启动时会附带一些命令参数。

3.1.2 shutdown.bat

shutdown.bat的内容与启动脚本startup.bat的内容基本一样。

其执行顺序也是先找到另一个批处理脚本catalina.bat的路径,然后执行catalina.bat。不同的是,执行catalina.bat时传入的参数不同,如启动时传入的参数为start,而关闭时传入的参数为stop

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_tomcat_17

3.1.3 catalina.bat

catalina.bat批处理脚本才是Tomcat服务器启动和关闭的核心脚本,它的最终目的是组合出一个最终的执行命令,组合时会涉及多个变量和组合逻辑。分成7部分进行讲解。

第一部分脚本如下所示,它主要目的是在按Ctrl+C组合键终止程序时自动确认。当执行catalina.bat run命令时开始启动Tomcat,然后如果按Ctrl+C组合键则会终止进程,而且命令窗口还会输出“终止批处理操作吗(YN)?"让用户确认,而这里做的就是帮你自动输入Y

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_客户端_18

第二部分脚本主要用于设置CATALINAHOME,CATALINA BASE两个变量。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_客户端_19

  • 第三部分主要用于尝试寻找setenv.bat和steclasspath.bat并执行它们。,然后再将Tomcat的启动包bootstrap.jar日志包tomcat-juli.jar添加到CLASSPATH环境变量下
  • 第四部分是对日志配置的设置。
  • 第五部分是执行命令前一些参数的初始化。
  • 第六部分命令主要根据不同的参数跳转到不同的位置执行不同的命令,其实也组装一些参数,为下一步真正执行命令做准备。
  • 第七部分属于命令真正执行的过程,它将前面所有脚本运行后组成一个最终的命令开始执行。

3.1.4 setclasspath.bat

在catalina.bat批处理脚本中会调setclasspath.bat批处理脚本, setclasspath.bat的职责很简单,它只负责寻找、检查JAVA-HOME和JRE HOME两个环境变量。

rem In debug mode we need a real JDK (JAVA_HOME)
if ""%1"" == ""debug"" goto needJavaHome

rem Otherwise either JRE or JDK are fine
if not "%JRE_HOME%" == "" goto gotJreHome
if not "%JAVA_HOME%" == "" goto gotJavaHome
echo Neither the JAVA_HOME nor the JRE_HOME environment variable is defined
echo At least one of these environment variable is needed to run this program
goto exit

:needJavaHome
rem Check if we have a usable JDK
if "%JAVA_HOME%" == "" goto noJavaHome
if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaHome
if not exist "%JAVA_HOME%\bin\jdb.exe" goto noJavaHome
if not exist "%JAVA_HOME%\bin\javac.exe" goto noJavaHome
set "JRE_HOME=%JAVA_HOME%"
goto okJava

:noJavaHome
echo The JAVA_HOME environment variable is not defined correctly.
echo It is needed to run this program in debug mode.
echo NB: JAVA_HOME should point to a JDK not a JRE.
goto exit

:gotJavaHome
rem No JRE given, use JAVA_HOME as JRE_HOME
set "JRE_HOME=%JAVA_HOME%"

3.2 Tomcat中的变量及属性

变量及属性的目的主要是将某些参数剥离出程序,以实现可配置性。

  • 在Tomcat中,启动时会涉及大量环境变量、JVM系统属性及Tomcat属性。环境变量在操作系统中配置,也可以在批处理中添加或修改环境变量,
    • 在Tomcat程序中可通过System.getenv(name)获取环境变量
  • JVM系统属性可以是JVM自带的属性,也可以在Java执行命令中通过-D参数配置,
    • 在Tomcat程序中可通过System.getProperty(name)获取JVM系统属性。
  • 而Tomcat属性主要通过catalina, properties配置文件配置,在Tomcat启动时会加载,
    • Tomcat程序通过CatalinaProperties获取。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_java_20

3.2.1 环境变量

3.2.2 JVM系统变量

3.2.3 Tomcat属性

第4章从整体预览Tomcat

4.1 整体结构及组件介绍

如果将Tomcat内核高度抽象,则它可以看成由连接器(Connector)组件容器(Container)组件组成,其中:

  • Connector组件负责在服务器端处理客户端连接,包括接收客户端连接、接收客户端的消息报文以及消息报文的解析等工作,
  • Container组件则负责对客户端的请求进行逻辑处理,并把结果返回给客户端。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_学习_21

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_tomcat_22

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_tomcat_23

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_java_24

4.2 请求处理的整体过程

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_套接字_25

Tomcat作为专门处理HTTP的Web服务器,而且使用阻塞IO方式接受客户端的连接。

  • 1.当Tomcat启动后, Connector组件的接收器(Acceptor)将会监听是否有客户端套接字连接并接收Socket.
  • 2一旦监听到客户端连接,则将连接交由线程池Executor处理,开始执行请求响应任务。
  • 3 HttpllProcessor组件负责从客户端连接中读取消息报文,然后开始解析HTTP的请求行、请求头部、请求体。将解析后的报文封装成Request对象,方便后面处理时通过Request对象获取HTTP协议的相关值。
  • 4 Mapper组件根据HTTP协议请求行的URL属性值和请求头部的Host属性值匹配由哪个Host容器、哪个Context容器、哪个Wrapper容器处理请求,这个过程其实就是根据请求从Tomcat中找到对应的Servlet,然后将路由的结果封装到Request对象中,方便后面处理时通过Request对象选择容器。
  • 5 CoyoteAdaptor组件负责将Connector组件和Engine容器连接起来,把前面处理过程中生成的请求对象Request和响应对象Response传递到Engine容器,调用它的管道
  • 6 Engine容器的管道开始处理请求,管道里包含若干阀门(Valve),每个阀门负责某些,处理逻辑。这里用xxxValve代表某阀门,我们可以根据自己的需要往这个管道中添加多个阀门,首先执行这个xxxValve,然后才执行基础阀门EngineValve,它会负责调用Host容器的管道。
  • 7  Host容器的管道开始处理请求,它同样也包含若干阀门,首先执行这些阀门,然后执行基础阀门HostValve,它继续往下调用Context容器的管道。
  • 8 Context容器的管道开始处理请求,首先执行若干阀门,然后执行基础阀门ContextValve,它负责调用Wrapper容器的管道。
  • 9 Wrapper容器的管道开始处理请求,首先执行若干阀门,然后执行基础阀门WrapperValve,它会执行该Wrapper容器对应的Servlet对象的处理方法,对请求进行逻辑处理,并将结果输出到客户端。以上便是一个客户端请求到达Tomcat后处理的整体流程。这里,先对其有个整体印象,后面会深入讨论更多的细节。
第5章Server组件与Service组件

Server组件和Service组件是Tomcat核心组件中最外层级的两个组件, Server组件可以看成Tomcat的运行实例的抽象,而Service组件则可以看成Tomcat内的不同服务的抽象。

5.1 Server组件

作为Tomcat最外层的核心组件, Server组件的作用主要有以下几个。

  • >提供了监听器机制,用于在Tomcat整个生命周期中对不同事件进行处理。

  • >提供了Tomcat容器全局的命名资源实现。

  • >监听某个端口以接收SHUTDOWN命令。

5.1.1 生命周期监听器 

为了在Server组件的某阶段执行某些逻辑,于是提供了监听器机制在Tomcat中实现一个生命周期监听器很简单,只要实现LifecycleListener接口即可,在lifecycleEvent方法中对感兴趣的生命周期事件进行处理

  • 1. AprLifecycleListener监听器在Tomcat初始化前,该监听器会尝试初始化APR库,假如能初始化成功,则会使用APR接受客户端的请求并处理请求。在Tomcat销毁后,该监听器会做APR的清理工作。
    • APR:Apache Server经过这么多年的发展后,将一些通用的运行时接口封装起来提供给大家,这就是Apache Portable Run-time libraries, APR。
  • 2. JasperListener监听器在Tomcat初始化前该监听器会初始化Jasper组件, Jasper是Tomcat的JSP编译器核心引擎,用于在Web应用启动前初始化Jasper。
  • 3, JreMemoryLeakPreventionListener监听器:该监听器主要提供解决JRE内存泄漏和锁文件的一种措施,该监听器会在Tomcat初始化时使用系统类加载器先加载一些类和设置缓存属性,以避免内存泄漏和锁文件。
    • 以将导致被引用的类加载器无法被回收,而Tomcat在重加载一个Web应用时正是通过实例化一个新的类加载器来实现的,旧的类加载器无法被垃圾回收器回收,导致内存泄漏。
  • 4, GlobalResourcesLifecycleListener监听器该监听器主要负责实例化Server组件里面JNDI资源的MBean,并提交由JMX管理。此监听器对生命周期内的启动事件和停止事件感兴趣,它会在启动时为JNDI创建MBean,而在停止时销毁MBean.
    • JNDI(Java Naming and Directory Interface)是一个​​应用程序​​设计的API,为开发人员提供了查找和访问各种命名和​​目录服务​​的通用、统一的接口,类似JDBC都是构建在抽象层上。现在JNDI已经成为J2EE的标准之一,所有的J2EE容器都必须提供一个JNDI的服务。
    • MBean:描述一个可管理的资源。是一个java对象,遵循以下一些规则:1.必须是公用的,非抽象的类 2.必须有至少一个公用的​​构造器​​ 3.必须实现它自己的相应的MBean接口或者实现javax.management.DynamicMBean接口4.可选的,一个MBean可以实现javax.management.NotificationBroadcaster接口MBean的类型
    • JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等​​植入​​管理功能的框架。JMX可以跨越一系列异构操作系统平台、​​系统体系结构​​和​​网络传输协议​​,灵活的开发无缝集成的系统、网络和服务管理应用。
  • 5. ThreadLocalLeakPreventionListener监听器该监听器主要解决ThreadLocal的使用可能带来的内存泄漏问题。该监听器会在Tomcat启动后将监听Web应用重加载的监听器注册到每个Web应用上,当Web应用重加载时.
  • 6. NamingContextListener监听器该监听器主要负责Server组件内全局命名资源在不同生命周期的不同操作,在Tomcat启动时创建命名资源、绑定命名资源,在Tomcat停止前解绑命名资源、反注册MBean.

5.1.2 全局命名资源

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_tomcat_26

这个完全不懂......:)

5.1.3 监听SHUTDOWN命令

Server会另外开放一个端口用于监听关闭命令,这个端口默认为8005,此端口与接收客户端请求的端口并非同一个。客户端传输的第一行如果能匹配关闭命令(默认为SHUTDOWN),则整个Server将会关闭。

Tomcat中有两类线程,一类是主线程,另外一类是daemon(守护)线程当Tomecat启动时, Server将被主线程执行,其实就是完成所有的启动工作,包括启动接收客户端和处理客户端报文的线程,这些线程都是daemon(daemon守护)线程

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_套接字_27

所有启动工作完成后,主线程将进入等待SHUTDOWN命令的环节,它将不断尝试读取客户端发送过来的消息,一旦匹配SHUTDOWN命令则跳出循环。主线程继续往下执行Tomcat的关闭工作。最后主线程结束,整个Tomcat停止。

5.2 Service组件

Service组件是若干Connector组件和Executor组件组合而成的概念。

  • Connector组件负责监听某端口的客户端请求,不同的端口对应不同的Connector,
  • Executor组件在Service抽象层面提供了线程池,让Service下的组件可以共用线程池。

默认情况下,不同的Connector组件会自己创建线程池来使用,而通过Service组件下的Executor组件则可以实现线程池共享,每个Connector组件都使用Service组件下的线程池。除了Connector组件之外,其他的组件也可以使用。

Tomcat中线程池的实现。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_java_28

一个线程池的属性起码包含初始化线程数量、线程数组、任务队列。

  1. 初始化线程数量指线程池初始化的线程数,
  2. 线程数组保存了线程池中的所有线程
  3. 任务队列指添加到线程池中等待处理的所有任务。

线程池里有两个线程,池里线程的工作就是不断循环检测任务队列中是否有需要执行的任务,如果有,则处理并移出任务队列。于是,可以说线程池中的所有线程的任务就是不断检测任务队列并不断执行队列中的任务。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_java_29

JUC就是java.util .concurrent工具包的简称。这是一个处理线程的工具包,JDK 1.5开始出现的。

  1. 创建线程的方式 --- 实现Callable接口
  2. 闭锁
  3. 锁分段机制
  4. volatile关键字与内存可见性

使用线程池时只须实例化一个对象,构造函数就会创建相应数量的线程并启动线程,启动的线程无限循环地检测任务队列,执行方法execute()仅仅把任务添加到任务队列中。,所有任务都必须实现Runnable接口,这是线程池的任务队列与工作线程的约定

JUC工具包作者Doug Lea当时如此规定,工作线程检测任务队列并调用队列的run()方法,假如你自己重新写一个线程池,就完全可以自己定义一个不一样的任务接口。一个完善的线程池并不像下面的例子那样简单,它需要提供启动、销毁、增加工作线程的策略,最大工作线程数,各种状态的获取等操作,而且工作线程也不可能始终做无用循环,需要对任务队列使用wait, notify优化,或者将任务队列改用为阻塞队列(生产者消费者模式)

第6章Connector组件

Connector (连接器)组件是Tomcat最核心的两个组件之一,主要的职责:

负责接收客户端连接和客户端请求的处理加工。每个Connector都将指定一个端口进行监听,分别负责对请求报文解析和对响应报文组装,解析过程生成Request对象,而组装过程则涉及Response对象。

如果将Tomcat整体比作一个巨大的城堡,那么Connector组件就是城堡的城门,每个人要进入城堡就必须通过城门,它为人们进出城堡提供了通道。同时,一个城堡还可能有两个或多个城门,每个城门代表了不同的通道。

connetor中包含Protocol组件、Mapper组件和CoyoteAdaptor组件。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_java_30

  1. Protocol组件是协议的抽象,它将不同通信协议的处理进行了封装,比如HTTP协议和AJP协议
  2. Endpoint是接收端的抽象,由于使用了不同的IO模式,因此存在多种类型的Endpoint,如
    1. BIO模式的JoEndpoint.
    2. NIO模式的NioEndpoint
    3. 本地库IO模式的AprEndpoint.
  3. Acceptor是专门用于接收客户端连接的接收器组件
  4. Executor则是处理客户端请求的线程池,Connector可能是使用了Service组件的共享线程池,也可能是Connector自己私有的线程池
  5. Processor组件是处理客户端请求的处理器,不同的协议和不同的IO模式都有不同的处理方式,所以有不同的processor。
  6. Mapper组件可以称为路由器,它提供了对客户端请求URL的映射功能,即可以通过它将请求转发到对应的Host组件、Context组件、Wrapper组件以进行处理并响应客户端,
  7. CoyoteAdaptor组件是一个适配器,它负责将Connector组件和Engine容器适配连接起来。
    1. 把接收到的客户端请求报文解析生成的请求对象和响应对象Response传递到Engine容器。

目前Tomcat支持两种Connector,分别是支持HTTP协议与AJP协议的Connector,用于接收和发送HTTP, AJP协议请求。

每个HTTP Connector实例对应一个端口,在同个Service实例内可以配置若干Connector实例,端口必须不同,但协议可以相同

  1. HTTP Connector包含的协议处理组件有
    • Http11Protocol (Java BIO模式)
    • Http11NioProtocol (Java NIO模式)
    • Http11AprProtocol (APR/native模式)

 Tomcat启动时根据server.xml的<Connector>节点配置IO模式,

AJP Connector组件用于支持AJP协议通信,当我们想将Web应用中包含的静态内容交给Apache处理时Apache与Tomcat之间的通信则使用AJP协议。

 AJPConnector包含的协议处理组件有

  • AipProtocol (Java BIO模式)
  • AjpNioProtocol (Java NIO模式)
  • AjpAprProtocol (APR/native模式)

6.1 HTTP阻塞模式协议-Http11Protocol

Htp11 Protocol 表示阻塞式的HTTP协议的通信,它包含从套接字连接接收、处理、响应客户端的整个过程。它主要包含JoEndpoint组件和Http Procesor 件。

6.1.1 套接字接收终端-JloEndpoint

负责启动某端口监听客户端的请求,负责接收套接字连接,负责提供一个线程池供系统处理接收到的套接字连接,负责对连接数的控制,负责安全与非安全套接字连接的实现等,

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_java_31

  • 1,连接数控制器--LimitLatch作为Web服务器,

T为了保证Web服务器不被冲垮,我们需要·采取一些保护措施,其中一种有效的方法就是采取流量控制。此处的流量更多地是指套接字的连接数,通过控制套接字连接个数来控制流量。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_客户端_32

Tomcat的流量控制器是通过AQS并发框架来实现的

思路是先初始化同步器的最大限制值,然后每接收一个套接字就将计数变量累加1,每关闭一个套接字将计数变量减1,如此一来,一旦计数变量值大于最大限制值,则AQS机制将会将接收线程阻塞,而停止对套接字的接收,直到某些套接字处理完关闭后重新唤起接收线程往下接收套接字。AQS即AbstractQueuedSynchronizer(抽象队列同步器),是一个用于构建锁和同步器的框架,


  • 2. Socket接收器-AcceptorAcceptor

主要的职责就是监听是否有客户端套接字连接并接收套接字,再将套接字交由任务执行器(Executor)执行。它不断从系统底层读取套接字,接着做尽可能少的处理,最后扔进线程池。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_tomcat_33

  • 3,套接字工厂-ServerSocketFactory

接收器Acceptor在接收连接的过程中,根据不同的使用场合可能需要不同的安全级别,例如安全性较高的场景需要对消息加密后传输,而在另外一些安全性要求较低的场合则无须对消息加密。反映到应用层则是使用HTTP与HTTPS的问题。

SSL/TLS协议为通信提供了以下服务;

  1. 提供验证服务,验证会话内实体身份的合法性;
  2. 提供加密服务,强加密机制能保证通信过程中的消息不会被破译;
  3. 提供防篡改服务,利用Hash算法对消息进行签名,通过验证签名保证通信内容不被改。

Servelt.xml中可以配置不用的服务器IO模型。和请求协议。http,https和ARP协议等。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_java_34

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_tomcat_35

  • 4·任务执行器-Executo

Tomcat中用于处理客户端请求的线程池-Executor.为确保整个Web服务器的性能,应该在接收到请求后以最快的速度把它转交到其他线程上去处理。在接收到客户端的请求后这些请求被交给任务执行器Executor,它是一个拥有最大最小线程数限制的线程池。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_套接字_36

Connector组件的Executor分为两种类型:

共享Executor和私有Executor.

所谓共享Executor则指直接使用Service组件的线程池,多个Connector可以共用这些丝程池。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_客户端_37

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_客户端_38

  • 5.任务定义器-SocketProcessor将套接字放进线程池前需要定义好任务,而需要进行哪些逻辑处理则由SocketProcessor定义,根据线程池的约定,作为任务必须扩展Runnable,

对套接字进行处理并输出响应报文;连接数计数器减一腾出通道;关闭套接字;

SocketProcessor的任务主要分为三个:

  1. 处理套接字并响应客户端,
  2. 连接数计数器减1,
  3. 关闭套接字。

其中对套接字的处理是最重要也是最复杂的,它包括对底层套接字字节流的读取,HTTP协议请求报文的解析(请求行、请求头部、请求体等信息的解析),根据请求行解析得到的路径去寻找相应虚拟主机上的Web项目资源,根据处理的结果组装好HTTP协议响应报文输出到客户端。此部分是Web容器处理客户端请求的核心。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_客户端_39

6.1.2 HTTP阻塞处理器-Http11Processor

Htp11Processor组件提供了对HTTP协议通信的处理,包括对套接字的读写和过滤,对HTTP协议的解析以及封装成请求对象, HTTP协议响应对象的生成等操作。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_套接字_40


6.2 HTTP非阻塞模式协议-Http11NioProtocol

Http11NioProtocol表示非阻塞模式的HTTP协议的通信,它包含从套接字连接接收、处理请求、响应客户端的整个过程。

它主要包含NioEndpoint组件和Http11NioProcessor组件。

启动时NioEndpoint组件将启动某个端口的监听,一个连接到来后将被注册到NioChannel队列中,

由Poller (轮询器)负责检测通道的读写事件,并在创建任务后扔进线程池中,线程池进行任务处理。

处理过程中将通过协议解析器Http11NioProcessor组件对HTTP协议解析,同时通过适配器(Adapter)匹配到指定的容器进行处理并响应客户端。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_套接字_41

6.2.1非阻塞接收端-NioEndpoint

NioEndpoint组件是非阻塞IO终端的一个抽象

NioEndpoint组件包含了·很多子组件。其中包括

  • LimitLatch (连接数控制器)、负责对连接数的控制
  • Acceptor (套接字接收器),负责接收套接字连接并注册到通道队列里面
  • Poller (轮询器)、负责轮询检查事件列表
  • Poller池、包含了若干Poller组件,
  • SocketProcessor (任务定义器)
  • 以及Executor (任务执行器),负责处理套接字的线程池。

1·连接数控制器-LimitLatch不管使用BIO模式还是NIO模式,作为服务器端的一个服务,不可能无限制地接收客 。

5·连接轮询器-PollerNIO模型需要同时对很多连接进行管理,管理的方式则是不断遍历事件列表,对相应连接的相应事件做出处理,而遍历的工作正是交给Poller负责。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_客户端_42

6.poller池

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_学习_43

6.2.2 HTTP非阻塞处理器-Httpl1NioProcessor


6.3 HTTP APR模式协议--Httpl1AprProtoco

6.3.1 APR接收终端-AprEndpoint

6.3.2 HTTP APR处理器-Httpl1AprProcessor .

6.4 AJP Connector

6.4.1 AJP阻塞模式协议-AjpProtoco

6.4.2 AJPAPR模式协议-AjpAprProtoco 

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_学习_44

6.5 HTTP三种模式的Connector

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_tomcat_45

6.6 AJP三种模式的Connector

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_学习_46

第7章 Engine 容器

Engine即为全局引擎容器,它的标准实现是StandardEngine。

它包含的主要组件有Host组件、AccessLog组件、Pipeline组件、Cluster组件、Realm组件、Lifecyclelistener组件和Log组件。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_java_47

第10章Wrapper容器

Wrapper属于Tomcat中4个级别容器中最小级别的容器,与之相对应的是Servlet, Servlet的概念对于我们来说非常熟悉,我们会在它的doGet和doPost等方法上编写逻辑处理代码,而Wrapper则负责调用映射这些方法的逻辑

一般来说,一个Wrapper对应一个Servlet对象,也就是说,所有处理线程都共用同一个Servlet对象。但按照规范,实现了SingleThreadModel接口的Servlet也允许多个对象存在,

Wrapper容器可能对应一个Servlet对象,也可能对应一个Servlet对象池。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_客户端_48

10.1 Servlet工作机制

Servlet在初始化时要调用init方法,在销毁时要调用destroy方法,而对客户端请求处理时则调用service方法。

对于这些机制,都必须由Tomcat在内部提供支持,具体则由Wrapper容器提供支持。对于Tomcat中消息流的流转机制,我们都已经比较清楚了, 4个不同级别的容器是通过管道机制进行流转的,对于每个请求都是一层一层处理的。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_客户端_49

如图10.2所示,当客户端请求到达服务端后,请求被抽象成Request对象后向4个容器进行传递,

  • 首先经过Engine容器的管道通过若干阀门,最后通过StandardEngineValve阀门流转到Host容器的管道,
  • 通过StandardHostValve阀门流转到Context容器的管道,
  • 通过StandardContextValve阀门流转到Wrapper容器的管道
  • 而对Servlet的核心处理也正是在StandardWrapperValve阀门中。

StandardWrapperValve阀门先由Application FilterChain组件执行过滤器,然后调用Servlet的service。

综上所述, Servlet工作机制的大致流程是:

  1. Request
  2. --》StandardEngine Valve
  3. --》StandardHostValve
  4. --》StandardContextValve
  5. --》StandardWrapperValve
  6. --》实例化并初始化Servlet对象
  7. --》由过滤器链执行过滤操作
  8. --》调用该Servlet对象的service方法
  9. Response.

10.2 Servlet对象池

Servlet在不实现SingleThreadModel的情况下以单个实例模式运行,如图10.3所示。这种情况下, Wrapper容器只会通过反射实例化一个Servlet对象,对应此Servlet的所有客户端请求都会共用此Servlet对象。而对于多个客户端请求Tomcat会使用多线程处理。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_套接字_50

为了支持一个Servlet对象对应一个线程, Servlet规范提出了一个SingleThreadModel接口,, Tomcat容器必须要完成的机制是:如果某个Servlet类实现了SingleThreadModel接口,则要保证一个线程独占一个Servlet对象

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_tomcat_51

10.3 过滤器链

Context容器的过滤器模块包含了过滤器的相关信息,

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_套接字_52

创建时过滤器链对象做了如下逻辑处理。

  • ①从Context容器中获取所有过滤器的相关信息。
  • 2通过URL匹配过滤器,匹配的加入到过滤器链中。
  • ③通过Servlet名称匹配过滤器,匹配的加入到过滤器链中。

创建ApplicationFilterChain对象后, StandardWrapper Valve将调用它的doFilter方法,它就会开始一个一个调用过滤器,请求被一层层处理,最后才调Servlet处理,过滤器链将Context中所有过滤器中对应该请求的过滤器串联起来,实现过滤器功能

10.4 Servlet种类

,一个请求到达Tomcat后将由URI映射器根据请求URI进行建模,在路由到Wrapper容器时会通过一定的算法选择不同的Servlet进行处理。比如,

  • 普通Servlet请求则路由到普通Servlet,
  • JSP页面则路由到JspServlet,
  • 而静态资源则路由到DefaultServlet

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_客户端_53

Servlet路径的匹配规则如下:

  • 首先,尝试使用精确匹配法匹配精确类型Servlet的路径。
  • 然后,尝试使用前缀匹配通配符类型Servlet.
  • 接着,尝试使用扩展名匹配通配符类型Servlet.

JspServlet处理逻辑大致如下:

  • 判断是不是第一次访问JSP,如果是,则会先编译JSP页面,按一定包和类命名规则生成对应的Servlet类。
  • 加载刚刚编译好的JSP Servlet类,并初始化它们。
  • 调用刚刚加载好的JSP Servlet的service方法,处理请求

DefaultServlet通过JNDI据URI在Tomea然后以该资源响应客户端

10.5 Comet模式的支持

Comet模式是一种服务器端推技术,它的核心思想提供一种能让当服务器端往客户端发送数据的方式

.....又使用了AJAX不断从客户端轮询服务器以更新数据,然后使用Comet模式由服务器端通过长连接推数据。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_java_54

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_学习_55

10.6 WebSocket协议的支持

WebSocket协议属于HTML5标准,图10.9为WebSocket协议通信的过程。 WebSocket协议摒弃了HTTP协议烦琐的请求头部,而是以数据帧的方式进行传输,效率更高,该连接支持双向通信,并且使用WebSocket协议的数据帧格式发送消息。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_客户端_56

握手过程需要说明。为了让WebSocket协议能和现有HTTP协议web架构互相兼容,WebSocket协议的握手要基于HTTP协议

10.异步Servlet

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_tomcat_57

当客户端请求到来时,首先通过管道,然后进入到Wrapper容器的管道,调用Servlet实例的service后,创·建一个异步上下文将耗时的逻辑操作封装起来,交给用户自己定义的线程池。这时, Tomcat的处理线程就能马上回到Executor线程池,而不用等待耗时的操作完成才释放线程,从而提升了Tomcat的整体处理能力。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_tomcat_58

第11章生命周期管理

11.1生命周期统一接口-Lifecycle

Tomcat中的组件使用Lifecycle管理启动、停止、关闭。

Tomcat内部架构中各个核心组件有包含与被包含的关系,例如, Server包含Service, Service包含Container和Connector,往下再一层层包含。

Tomcat就是以容器的方式来组织整个系统架构的,就像数据结构的树,树的根节点没有父节点,其他节点有且仅有一个父节点,每个父节点有零个或多个子节点。

通过父容器启动它的子容器,这样只要启动根容器,即可把其他所有容器都启动,达到统一启动、停止、关闭的效果。

作为统一的接口, Lifecycle把所有的启动、停止、关闭、生命周期相关的方法都组织到一起,就可以很方便地管理Tomcat各个容器组件的生命周期。

在初始化阶段,根容器Server组件会调用init方法,而在init方法里会调用它的子容器Service组件的init方法,以此类推。比如, Tomcat的Server组件的init负责遍历调用其包含的所有Service组件的init

11.2生命周期的状态转化

从NEW到DESTROYED中间经历了生命周期的各个状态,这样就可以把整个生命周期划分为多个阶段,每个阶段完成每个阶段的任务。假如一个容器调用init()后,状态的转化是NEW-INITIALIZING-INITIALIZED,其中从INITIALIZING到INITIALIZED是自动变化的,并不需要人为操作。接着调用start),状态则变化为INITIALIZED-STARTINGPREP-STARTING-STARTED,这个过程全部自动完成。接下来,如果调用stop),状态变化就为STARTED-STOPPING PREP-STOPPING-STOPPED,如果在生命周期的某个阶段发生意外,则可能经历xx-DESTROYING-DESTROYED,整个生命周期内状态的转化相对较复杂。

Tomcat 学习笔记(《Tomcat内核设计剖析》读书笔记)_java_59


11.3生命周期事件监听机制

Tomcat使用了事件监听器模式来实现。

一般来说,事件监听器需要三个参与者

事件对象,用于封装事件的信息,在事件监听器接口的统一方法中作为参数使用,一般继承java. util.EventObject类。

事件源:触发事件的源头,不同的事件源会触发不同的事件类型。

事件监听器,负责监听事件源发出的 事件,更确切地说,应该是每当发生事件时,事件源就会调用监听器的统一方法去处理,监听器一般实现java.util.EventListener接口。

事件源提供注册事件监听器的方法,维护多个事件监器听对象,同时可以向事件监听器对象发送事件对象。伴随着事件的发生,相应的状态信息都封装在事件对象中,事件源将事件对象发给已经注册的所有事件监听器,这里其实是调用事件监听器的统一方法,把事件对象作为参数传过去。接着会在这个统一方法里根据事件对象做出相应处理。

嗯,走马观花的看了一遍,以后有机会在一点点看吧。 加油。生活。 2020.8.10