《Tomcat内核设计剖析》读书笔记

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

第1章Web服务器机制

.1 通信协议

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

1.1.1 HТТP/HTTPS.

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

HTTPS简单地说就是HTTP的安全版。通常,在安全性要求比较高的网站(例如银行网站)上会看到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算法,这是一种不可逆的算法,它常用于验证数据的完整性。

 

1.1.2 HTTP请求/响应模型.

从某种意义上来说, HTTP协议永远都由客户端发起请求,由服务器进行响应并发送回响交报文。如果没有客户端进行请求或曾经请求过,那么服务器是无法将消息推送到客户端的。

1.1.3 解析HTTP报文

 

1.2 套接字通信

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

一般这种抽象层由操作系统提供或者由JVM自己实现。使用套接字通信可以简单地实现应用程序在网络上的通信。一台机器上的应用向套接字中写入信息,另外一台相连的机器能读取到。

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

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

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

1.2.1 单播通信

单播通信是网络节点之间通信方式的一种。单个网络节点与单个网络节点之间的通信就称为单播通信。它是一种一对一的模式,发送、接收信息只在两者之间进行,同时它也是最常见的一种通信。

浏览网页访问服务器时发生的通信属於单播通信,报文的发送与接收发生在你的电脑与网站的服务器之间。

首先,绑定本地8888端口,然后调用accept)方法进行阻塞,等待客户端的连接,一旦有1连接到来就创建一个套接字并返回。接着,获取输入/输出流,输入流用于获取客户端传输的,数据,而输出流则用来向客户端响应发送数据,处理完后关闭套接字。为了简化代码,这里完成一次响应后便把ServerSocket关闭。

服务器端的8888端口已经处于监听状态,客户端如果要与之通信,只须简单地先指定服·务器端IP与端口号以实例化一个套接字,然后获取套接字的输出流与输入流。输出流用于向送数据,输入流用于读取服务器发送过来的数据。交互处理完后关闭套接字。

1.2.2 组播通信

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

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

  • 组播通信中最重要的内容是如何维护路由器与主机之间的关系,其主要通过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模型

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

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

该模型的特点:

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

1.3.2 多线程阻塞IO模型

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

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

1.3.3 单线程非阻塞IO模型

单线程非阻塞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 多线程非阻塞1/0模型

Reactor模式

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

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

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

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

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

基於单线程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接口

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接口

2.5 Filter接口

2.6会话

2.7注解 

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部署描述文件

第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. 接收参数,在启动时会附带一些命令参数。

3.1.2 shutdown.bat

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

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

3.1.3 catalina.bat

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

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

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

  • 第三部分主要用于尝试寻找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获取。

3.2.1 环境变量

3.2.2 JVM系统变量

3.2.3 Tomcat属性

第4章从整体预览Tomcat

4.1 整体结构及组件介绍

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

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

4.2 请求处理的整体过程

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

  • 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 全局命名资源

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

5.1.3 监听SHUTDOWN命令

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

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

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

5.2 Service组件

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

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

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

Tomcat中线程池的实现。

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

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

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

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组件。

  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阻塞模式协议-Httpl1Protocol

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

6.1.1 套接字接收终端-JloEndpoint

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

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

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

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

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

  • 2. Socket接收器-AcceptorAcceptor

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

6.1.2 HTTP阻塞处理器-Http11Processor

6.2 HTTP非阻塞模式协议-Httpl1NioProtocol 

6.2.1非阻塞接收端-NioEndpoint

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 

6.5 HTTP三种模式的Connector

6.6 AJP三种模式的Connector

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章