Tomcat系列文章:http://www.cnblogs.com/f-ck-need-u/p/7576137.html
tomcat可以处理静态资源的请求,也可以通过servlet处理动态资源的请求。处理jsp动态资源时,先通过jasper组件(具体的是JspServlet)将jsp翻译成java源代码并编译成class后运行。需要知道的是,静态资源也一样是通过servlet处理的,只不过它使用的servlet是定义在$catalina_home/conf/web.xml中默认的servlet。本文将详细分析tomcat如何处理客户端请求(并发)以及如何处理动、静态资源。
如下两图:上面的图是tomcat组件体系的简图,下面的图是Service组件细化后的图。
其中:
server
组件是管理tomcat实例的组件,可以监听一个端口,从此端口上可以远程向该实例发送shutdown关闭命令。service
组件是一个逻辑组件,用于绑定connector和container,有了service表示可以向外提供服务,就像是一般的daemon类服务的service。可以认为一个service就启动一个JVM,更严格地说,一个engine组件才对应一个JVM(定义负载均衡时,jvmRoute就定义在Engine组件上用来标识这个JVM),只不过connector也工作在JVM中。connector
组件是监听组件,它有四个作用:
container
是容器,它是一类组件,在配置文件(如server.xml)中没有体现出来。它包含4个容器类组件:engine容器、host容器、context容器和wrapper容器。engine
容器用于从connector组件处接收转发过来的请求,然后按照分析的结果将相关参数传递给匹配出的虚拟主机。engine还用于指定默认的虚拟主机。host
容器定义虚拟主机,由于tomcat主要是作为servlet容器的,所以为每个webapp指定了它们的根目录appBase。context
容器主要是根据path和docBase获取一些信息,将结果交给其内的wrapper组件进行处理(它提供wrapper运行的环境,所以它叫上下文context)。一般来说,都采用默认的标准wrapper类,因此在context容器中几乎不会出现wrapper组件。wrapper
容器对应servlet的处理过程。它开启servlet的生命周期,根据context给出的信息以及解析web.xml中的映射关系,负责装载相关的类,初始化servlet对象init()、执行servlet代码service()以及服务结束时servlet对象的销毁destory()。executor
组件为每个Service组件提供线程池,使得Engine可以从线程池中获取线程处理请求,从而实现tomcat的并发处理能力。一定要注意,Executor的线程池大小是为Engine组件设置,而不是为Connector设置的,Connector的线程数量由Connector组件的acceptorThreadCount属性来设置。如果要在配置文件中设置该组件,则必须设置在Connector组件的前面,以便在Connector组件中使用executor
属性来引用配置好的Executor组件。如果不显式设置,则采用Connector组件上的默认配置,默认配置如下:
根据上面描述的tomcat组件体系结构,处理请求的大致过程其实很容易推导出来:
Client(request)-->Connector-->Engine-->Host-->Context-->Wrapper(response data)-->Connector(response header)-->Client
在监听和处理请求上,tomcat和httpd/nginx等服务程序不一样,而且是巨大的区别。因此,在理解处理请求时,万万不可将httpd/nginx的处理模式套在tomcat上。
关于httpd/nginx等服务程序处理连接时的过程,此处仅简单说明以体现它们和tomcat的不同之处,详细内容可参见我另一篇文章:不可不知的socket和TCP连接过程。
(1).httpd/nginx等都是监听进程/线程负责监听,当监听到连接请求时,将生成一个新的已连接套接字
放进一个称为已连接队列中,然后监听进程/线程继续回去监听。而负责处理请求的工作进程/线程则从该队列中获取已连接套接字
并与客户端建立TCP连接,然后与客户端进行通信,包括接收客户端的资源请求数据、构建和响应数据给客户端。
(2).tomcat虽然也将监听和处理请求的工作分别使用不同的组件进行处理,但connector线程监听到请求就直接建立TCP连接,并一直与客户端保持该连接。connector线程会分析请求并将结果转发给与之绑定的Engine组件,Engine线程负责处理请求以及构建响应数据,但Engine组件不会和客户端建立任何连接。Engine的一切数据来源都是Connector,客户端任何一次资源请求都会发送到connector上,并从connector转发给Engine。Engine构建响应后,再次将响应数据转发给Connector,并由Connector做一些处理(如加上首部字段)回复给客户端。
只要明确一点即可推导出tomcat的连接和请求处理机制:任何一次从外界流入的请求都必将经过connector,任何一次从本地流出的响应数据也都必将经过connector。这正是连接器的意义所在──连接客户端和服务端servlet。
connector组件支持4种IO协议类型:同步阻塞BIO、同步非阻塞NIO、异步非阻塞NIO2、apache基金会提供的IO模型APR(IO模型只是APR类库的其中一种功能模块)。它们的区别如下图所示,除了BIO,其他IO模型在接受新请求上都是非阻塞的,因此这里不考虑BIO,而且现在也不会有人将connector设置成BIO模式。
该表中,最需要关注的是"Wait for next Request"行,NIO/NIO2/APR都是Non Blocking,这表示正在处理某个请求时不会被阻塞,可以接收额外的请求,这是tomcat实现并发处理请求的关键。
再来看connector组件和并发数量有关的设置选项:
acceptorThreadCount
:用于接收连接请求的线程数。默认值为1。多核CPU系统应该增大该值,另外由于长连接的存在,也应该考虑增大该值。maxThreads
:线程池中最多允许存在多少线程用于处理请求。默认值为200。它是最大并发处理的数量,但不影响接收线程接收更多的连接。maxConnections
:服务端允许接收和处理的最大连接数。当达到该值后,操作系统还能继续接收额外acceptCount个的连接请求,但这些连接暂时不会被处理。当Connector类型为BIO模型时的默认值等于maxThread的值,当为NIO/NIO2模型时的默认值为10000,当APR时默认长度为8192。acceptCount
:当所有请求处理线程都处于忙碌状态时,连接请求将进入等待队列,该值设置等待队列的长度。当达到队列最大值后,如果还有新连接请求进入,则会被拒绝。默认队列长度为100。
从上面几个属性的意义来分析并发机制:
再来细分一下tomcat和httpd/nginx的不同点:
已连接套接字
,不会和客户端直接建立TCP连接。而tomcat的监听者connector线程不仅会监听,还会直接建立TCP连接,且一直处于ESTABLISHED状态直到close。因此,tomcat处理连接的过程如下图所示,其中我把Engine线程处理请求的过程用"Engine+N"来表示,例如Engine线程1下的Engine1表示该Engine线程处理的某个请求,Engine2表示该线程处理的另一个请求。
假设tomcat的配置如下,其中项目名称为"xiaofang"。
当客户端访问http://www.xiaofang.com:8080/xuexi/abc.jsp
时,其请求的是$CATALINA_HOME/webapps/xiaofang/xuexi/abc.jsp文件。
(1).Connector组件扮演的角色。
Connector组件首先监听到该请求,于是建立TCP连接,并分析该请求。Connector分析请求的内容包括请求的协议、端口、参数等。因为这里没考虑集群问题,因此只可能是http协议而不可能是ajp协议的请求。分析后,将请求和相关参数转发给关联的Engine组件进行处理。
(2).Engine组件扮演的角色。
Engine组件主要用于将请求分配到匹配成功的虚拟主机上,如果没有能匹配成功的,则分配到默认虚拟主机上。对于上面的请求,很显然将分配到虚拟主机www.xiaofang.com
上。
(3).Host组件扮演的角色。
Host组件收到Engine传递过来的请求参数后,将对请求中的uri与Context中的path进行匹配,如果和某个Context匹配成功,则将请求交给该Context处理。如果匹配失败,则交给path=""
对应的Context来处理。所以,根据匹配结果,上面的请求将交给
进行处理。
注意,这次的uri匹配是根据path进行的匹配,它是目录匹配,不是文件匹配。也就是说,只匹配到uri中的xuexi就结束匹配。之所以要明确说明这一点,是因为后面还有一次文件匹配,用于决定交给哪个Servlet来处理。
(4).Context和Wrapper组件扮演的角色。
到了这里,就算真正到了Servlet程序运行的地方了,相比于前面几个组件,这里的过程也更复杂一些。
请求http://www.xiaofang.com:8080/xuexi/abc.jsp
经过Host的uri匹配后,分配给
进行处理,此时已经匹配了url中的目录,剩下的是abc.jsp。abc.jsp也需要匹配,但这个匹配是根据web.xml中的配置进行匹配的。
首先,从项目名为xiaofang的私有web.xml中进行查找,即webapps/xiaofang/WEB-INF/web.xml。由于此处仅为简单测试,因此并没有该文件。
于是从全局web.xml即$CATALINA_HOME/conf/web.xml中匹配abc.jsp。以下是web.xml中能匹配到该文件名的配置部分。
jsp *.jsp *.jspx jsp org.apache.jasper.servlet.JspServlet
首先根据
中的url-pattern进行文件匹配,发现该url匹配的是servlet-name为"jsp"的servlet,然后再找到与该名称对应的
标签段,发现处理该动态资源的类为org.apache.jasper.servlet.JspServlet
,于是找到该类对应的class文件,该class文件归档在$catalina_home/lib/jasper.jar中。
JspServlet程序的作用是将jsp文件翻译成java源代码文件,并放在$catalina_home/work目录下。然后将该java源文件进行编译,编译后的class文件也放在work目录下。这个class文件就是abc.jsp最终要执行的servlet小程序。
[root@xuexi ~]# ls /usr/local/tomcat/work/Catalina/www.xiaofang.com/xuexi/org/apache/jsp/
index_jsp.class index_jsp.java new_
在翻译后的servlet小程序中,不仅会输出业务逻辑所需的数据,还会输出html/css代码,这样一来,客户端接收到的数据都将是排版好的。
对于tomcat来说,无论是动态还是静态资源,都是经过servlet处理的。只不过处理静态资源的servlet是默认的servlet而已。
在$catalina_home/conf/web.xml中关于静态资源处理的配置如下。
default / default org.apache.catalina.servlets.DefaultServlet
需要记住的是,web.xml中的url-pattern是文件匹配,而server.xml中的
是目录匹配。
上面web.xml中的
表示的是默认servlet。这意味着,当web.xml中没有servlet-mapping能匹配请求url中的路径时,将匹配servlet-name,即名为default的servlet。然后找到处理default的类为org.apache.catalina.servlets.DefaultServlet
,该类的class文件归档在$catalina_home/lib/catalina.jar中。该servlet不像JspServlet会翻译jsp文件,它只有最基本的作用:原样输出请求文件中的内容给客户端。
例如,根据前面的配置,下面几个请求都将采用默认servlet进行处理,即当作静态资源处理。
http://www.xiaofang.com:8080/xuexi/index.html
http://www.xiaofang.com:8080/xuexi/abc.js http://www.xiaofang.com:8080/xuexi/index http://www.xiaofang.com:8080/xuexi/index.txt
但http://www.xiaofang.com:8080/xuexi
则不一定,因为tomcat中默认的index文件包含index.jsp和index.html,而index.jsp排在index.html的前面,只有不存在index.jsp时才请求index.html。
转载请注明出处:https://www.cnblogs.com/f-ck-need-u/p/8408670.html