前言
本文主要内容如下:
从整体预览Tomcat
如果将Tomcat内核高度抽象,则它可以看成由连接器(Connector)组件和容器(Container)组件组成,其中Connector组件负责在服务器端处理客户端连接,包括接收客户端连接、接收客户端的消息报文以及消息报文的解析等工作,而Container组件则负责对客户端的请求进行逻辑处理,并把结果返回给客户端。
Container组件包括4个级别的容器:Engine组件、Host组件、Context组件和Wrapper组件。
从Tomcat服务器配置文件server.xml的内容格式看,它所描述的Tomcat也符合上述的层级结构,以下便是server.xml简洁的配置节点,所以从server.xml文件也能看出Tomcat的大体结构。
<? xml version='1.0' encoding='utf-8'? >
<Server>
<Listener/>
<GlobalNamingResources>
<Resource/>
</GlobalNamingResources>
<Service>
<Executor/>
<Connector/>
<Engine>
<Cluster/>
<Realm/>
<Host>
<Context/>
</Host>
</Engine>
</Service>
</Server>
Server组件
Server是最顶级的组件,它代表Tomcat的运行实例,在一个JVM中只会包含一个Server。在Server的整个生命周期中,不同阶段会有不同的事情要完成。为了方便扩展,它引入了监听器方式,所以它也包含了Listener组件。另外,为了方便在Tomcat中集成JNDI,引入了GlobalNamingResources组件。同时,还包含了Service核心组件。
Service组件
Service是服务的抽象,它代表请求从接收到处理的所有组件的集合。如图所示,在设计上Server组件可以包含多个Service组件,每个Service组件都包含了若干用于接收客户端消息的Connector组件和处理请求的Engine组件。
其中,不同的Connector组件使用不同的通信协议,如HTTP协议和AJP协议。若干Connector组件和一个客户端请求处理组件Engine组成的集合即为Service。此外,Service组件还包含了若干Executor组件,每个Executor都是一个线程池,它可以为Service内所有组件提供线程池执行任务。
Connector组件
Connector主要的职责就是接收客户端连接并接收消息报文,消息报文经由它解析后送往容器中处理。如图所示,因为存在不同的通信协议,例如HTTP协议、AJP协议等,所以我们需要不同的Connector组件,每种协议对应一个Connector组件。
Connector组件的内部实现也会根据网络I/O的不同方式而不同分为阻塞I/O和非阻塞I/O。下面以HTTP协议为例,看看阻塞I/O和非阻塞I/O的Connector内部实现模块有什么不同。
在阻塞I/O方式下,Connector的结构如图所示。
- Http11Protocol组件,是HTTP协议1.1版本的抽象,它包含接收客户端连接、接收客户端消息报文、报文解析处理、对客户端响应等整个过程。它主要包含JIoEndpoint组件和Http11Processor组件。启动时,JIoEndpoint组件内部的Acceptor组件将启动某个端口的监听,一个请求到来后将被扔进线程池Executor,线程池进行任务处理,处理过程中将通过Http11Processor组件对HTTP协议解析并传递到Engine容器继续处理。
- Mapper组件,客户端请求的路由导航组件,通过它能对一个完整的请求地址进行路由,通俗地说,就是它能通过请求地址找到对应的Servlet。
- CoyoteAdaptor组件,一个将Connector和Container适配起来的适配器。
如图所示,在非阻塞I/O方式下,Connector的结构类似阻塞模式,Http11Protocol组件改成Http11NioProtocol组件,JIoEndpoint组件改成NioEndpoint, Http11Processor组件改成Http11NioProcessor组件,这些类似的组件的功能也都类似。唯独多了一个Poller组件,它的职责是在非阻塞I/O方式下轮询多个客户端连接,不断检测、处理各种事件,例如不断检测各个连接是否有可读,对于可读的客户端连接则尝试进行读取并解析消息报文。
Engine组件
Tomcat内部有4个级别的容器,分别是Engine、Host、Context和Wrapper。Engine代表全局Servlet引擎,每个Service组件只能包含一个Engine容器组件,但Engine组件可以包含若干Host容器组件。
除了Host之外,它还包含如下组件。
- Listener组件:可以在Tomcat生命周期中完成某些Engine容器相关工作的监听器。
- AccessLog组件:客户端的访问日志,所有客户端访问都会被记录。
- Cluster组件:它提供集群功能,可以将Engine容器需要共享的数据同步到集群中的其他Tomcat实例上。
- Pipeline组件:Engine容器对请求进行处理的管道。
- Realm组件:提供了Engine容器级别的用户-密码-权限的数据对象,配合资源认证模块使用。
Host组件
Tomcat中Host组件代表虚拟主机,这些虚拟主机可以存放若干Web应用的抽象(Context容器)。
Context组件
Context组件是Web应用的抽象,我们开发的Web应用部署到Tomcat后运行时就会转化成Context对象。它包含了各种静态资源、若干Servlet(Wrapper容器)以及各种其他动态资源。
Wrapper组件
Wrapper容器是Tomcat中4个级别的容器中最小的,与之相对应的是Servlet,一个Wrapper对应一个Servlet。它包含如下组件。
- Servlet组件:Servlet即Web应用开发常用的Servlet,我们会在Servlet中编写好请求的逻辑处理。
- ServletPool组件:Servlet对象池,当Web应用的Servlet实现了SingleThreadModel接口时则会在Wrapper中产生一个Servlet对象池。线程执行时,需先从对象池中获取到一个Servlet对象,ServletPool组件能保证Servlet对象的线程安全。
- Pipeline组件:Wrapper容器对请求进行处理的管道。
Tomcat的请求流转过程
这里假定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对象的处理方法,对请求进行逻辑处理,并将结果输出到客户端。以上便是一个客户端请求到达Tomcat后处理的整体流程。