在深入学习Tomcat之前,先了解一下它的整体结构以及各个组件,这对以后看源码会非常的有帮助。本笔记的主要参考资料是《Tomcat内核设计剖析》。
Tomcat整体结构
首先看下Tomcat源码中conf目录下的server.xml的层级结构,如下所示。
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
从上面xml配置文件中可以看出,Tomcat的整体结构与下图十分的相似,但是与下图相比还是少了Context组件。在Context组件中包含了Wrapper组件,Wrapper组件里看见了非常熟悉的Servlet。
这就是Tomcat的整体结构了。首先简单了解一下每个组件的作用,每个组件具体的功能以及源码实现后续再进行研究。
1.Server组件
从Tomcat层次结构图可以看见,Server是最顶层的组件。它代表着Tomcat运行实例并且在一个JVM中只会包含一个Server。Server组件中还包含Listener组件、GlobalNamingResourcs组件和核心组件Service。引入Listener组件是为了方便扩展,引入GlobalNamingResourcs时为了方便在Tomcat中集成JNDI。
2.Service组件
Service是服务的抽象,它代表请求从接收到处理的所有组件的集合。在设计上Server组件可以包含多个Service组件。如下xml所示,每个Service组件都包含了若干用于接收客户端消息的Connector组件和处理请求的Engine组件。其中Connector组件定义了通信协议,不同的Connector组件使用不同的协议。Engine组件用来处理请求。若干Connector组件和一个客户端请求处理组件Engine组成的集合即为Service组件。另外,Service组件还包含Executor组件,每个Executor组件是一个线程池,它可以为Service内所有组件提供线程池执行任务。
<Service name="Catalina">
<!--定义不同的通信协议-->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<!--用于处理请求-->
<Engine name="Catalina" defaultHost="localhost">
...
</Engine>
</Service>
3.Connector组件
Connector主要的职责就是接收客户端连接并接收消息报文,消息报文经由它解析后送往容器中处理。因为存在不同的通信协议,所以Tomcat可以配置不同的Connector组件。目前Tomcat包含Http和AJP两种协议的Connector。
<!--定义不同的通信协议-->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
对于Http协议Connector组件内部会根据网络I/O的不同方式分为BIO、NIO、APR、AIO四种。Tomcat7之前默认使用BIO、8之后默认采用NIO模式。
4.Engine组件
Tomcat的内部有4个级别的容器,分别是Engine、Host、Context和Wrapper。Engine代表全局Servlet引擎,每个Service组件只能包含一个Engine组件。Engine可以包含多个Host组件。它还包含以下组件:
- Listener组件:某些与Engine容器相关的监听
- AccessLog组件:记入客户端的访问日志
- Cluster组件:提供集群功能,可以将Engine容器需要共享的数据同步到集群中的其他Tomcat实例上
- Pipline组件:Engine组件对请求进行处理的管道
- Realm组件:提供了Engine容器级别的用户-密码-权限的数据对象,配合资源认证模块使用
5.Host组件
Host组件代表虚拟主机,这些虚拟主机可以存放若干Web应用的抽象(Context容器)。它还包含以下组件:
- Listener组件:某些与Engine容器相关的监听
- AccessLog组件:记入访问该虚拟主机上Web应用的客户端的访问日志
- Cluster组件:提供集群功能,可以将Host容器需要共享的数据同步到集群中的其他Tomcat实例上
- Pipline组件:Host组件对请求进行处理的管道
- Realm组件:提供了Host容器级别的用户-密码-权限的数据对象,配合资源认证模块使用
6.Context组件
Context组件是Web应用的抽象,我们开发的Web应用部署到Tomcat后运行时就会转化成Context对象。它包含了各种静态资源、若干Servlet(Wrapper容器)以及各种其他动态资源。它主要包括如下组件。
- Listener组件:可以在Tomcat生命周期中完成某些Context容器相关工作的监听器。
- AccessLog组件:客户端的访问日志,对该Web应用的访问都会被记录。
- Pipeline组件:Context容器对请求进行处理的管道。
- Realm组件:提供了Context容器级别的用户-密码-权限的数据对象,配合资源认证模块使用。
- Loader组件:Web应用加载器,用于加载Web应用的资源,它要保证不同Web应用之间的资源隔离。
- Manager组件:会话管理器,用于管理对应Web容器的会话,包括维护会话的生成、更新和销毁。
- NamingResource组件:命名资源,它负责将Tomcat配置文件的server.xml和Web应用的context.xml资源和属性映射到内存中。
- Mapper组件:Servlet映射器,它属于Context内部的路由映射器,只负责该Context容器的路由导航。
- Wrapper组件:Context的子容器。
7.Wrapper组件
Wrapper容器是Tomcat中4个级别的容器中最小的,与之相对应的是Servlet,一个Wrapper对应一个Servlet。它包含如下组件。
- Servlet组件:Servlet即Web应用开发常用的Servlet,我们会在Servlet中编写好请求的逻辑处理
- ServletPool组件:Servlet对象池,当Web应用的Servlet实现了SingleThreadModel接口时则会在Wrapper中产生一个Servlet对象池。线程执行时,需先从对象池中获取到一个Servlet对象,ServletPool组件能保证Servlet对象的线程安全。
- ServletPool组件:Servlet对象池,当Web应用的Servlet实现了SingleThreadModel接口时则会在Wrapper中产生一个Servlet对象池。线程执行时,需先从对象池中获取到一个Servlet对象,ServletPool组件能保证Servlet对象的线程安全。
请求处理的整体流程
这里假定Tomcat作为专门处理HTTP的Web服务器,而且使用阻塞I/O方式接受客户端的连接。下面介绍请求流转的具体过程。
① 当Tomcat 启动后,Connector组件的接收器(Acceptor)将会监听是否有客户端套接字连接并接收Socket。
② 一旦监听到客户端连接,则将连接交由线程池Executor处理,开始执行请求响应任务。
③ Http11Processor组件负责从客户端连接中读取消息报文,然后开始解析HTTP的请求行、请求头部、请求体。将解析后的报文封装成Request 对象,方便后面处理时通过Request对象获取HTTP协议的相关值。
④ Mapper组件根据HTTP协议请求行的URL属性值和请求头部的Host属性值匹配由哪个Host容器、哪个Context容器、哪个Wrapper容器处理请求,这个过程其实就是根据请求从Tomcat中找到对应的Servlet。然后将路由的结果封装到Request对象中,方便后面处理时通过Request对象选择容器。
⑤ CoyoteAdaptor组件负责将Connector组件和Engine容器连接起来,把前面处理过程中生成的请求对象Request和响应对象Response传递到Engine容器,调用它的管道。
⑥ Engine容器的管道开始处理请求,管道里包含若干阀门(Valve),每个阀门负责某些处理逻辑。这里用xxxValve代表某阀门,我们可以根据自己的需要往这个管道中添加多个阀门,首先执行这个xxxValve,然后才执行基础阀门EngineValve,它会负责调用Host容器的管道。
⑦ Host容器的管道开始处理请求,它同样也包含若干阀门,首先执行这些阀门,然后执行基础阀门HostValve,它继续往下调用Context容器的管道。
⑧ Context容器的管道开始处理请求,首先执行若干阀门,然后执行基础阀门ContextValve,它负责调用Wrapper容器的管道。
⑨ Wrapper容器的管道开始处理请求,首先执行若干阀门,然后执行基础阀门WrapperValve,它会执行该Wrapper容器对应的Servlet对象的处理方法,对请求进行逻辑处理,并将结果输出到客户端。