基础
软件架构 | 说明 | 示例 |
---|---|---|
C/S | 客户端/服务器端 | QQ,360... |
B/S | 浏览器/服务器端 | 淘宝网... |
网络资源 | 说明 | 示例 |
---|---|---|
静态资源 | 用户访问后得到的结果都是一样的,可以直接被浏览器解析。 | HTML/CSS/JS/JPG... |
动态资源 | 用户访问相同资源后得到的结果各不一样。动态资源访问后需先转换成为静态资源再返回给浏览器,通过浏览器进行解析。 | Servlet/JSP/PHP/ASP... |
网络通信三要素
网络通信 | 说明 |
---|---|
传输协议 | 数据传输的规则 |
主机地址 | IP地址,计算机在网络中的唯一标识。 |
端口号 | 应用程序在计算机中的唯一标识,取值范围0~65536。 |
Web服务器
服务器是指安装服务器软件的计算机,服务器软件是指用来接收用户请求、处理请求、作出响应的软件。而Web服务器软件是指可以部署Web项目,让用户通过浏览器可以访问的软件。
Web服务器软件 | 说明 |
---|---|
WebLogic | Oracle,收费,大型JavaEE服务器,支持JavaEE规范。 |
WebSphere | IBM,收费,大型JavaEE服务器,支持JavaEE规范。 |
JBOSS | JBOSS,收费,大型JavaEE服务器,支持JavaEE规范。 |
Tomcat | Apache基金会组织,开源,中小型JavaEE服务器,仅支持少量JavaEE规范,比如Servlet、JSP。 |
Tomcat历史
Tomcat最初由SUN公司的软件架构师James Duncan Davidson开发,称为JavaWebServer。1999年在Davidson帮助下该项目于1991年与Apache软件基金会旗下的JServ项目合并,其后发不了第一个版本3.x,也就是现在的Tomcat,该版本实现了Servlet2.2和JSP1.1规范。
2001年Tomcat发布了4.0,作为里程碑式的版本,Tomcat完全重新设计了其架构,并实现了Servlet2.3和JSP1.2规范。
Tomcat安装配置
架构
HTTP工作原理
- HTTP协议是浏览器与服务器之间的数据传输协议
- HTTP是基于TCP/IP协议来传递数据的应用层协议
- HTTP不涉及数据包
packet
的传输,主要规定了客户端和服务器之间的通信格式。
![](https://i-blog.csdnimg.cn/blog_migrate/bd1e220eac516c0b677aee43496bc044.png)
Tomcat整体架构
浏览器发送给服务器一个HTTP格式的请求后,HTTP服务器会接受到这个请求,需要调用服务端程序来处理,所谓的服务端程序也就是程序员编写的Java类,一般而言不同的请求需要由不同的Java类来处理。
![](https://i-blog.csdnimg.cn/blog_migrate/0e365d4498a2b09528c265bbf9ad8c27.png)
如果HTTP服务器直接调用业务类会直接产生紧耦合关系,为了解耦,HTTP服务器不直接调用业务类而是将请求交给Servlet容器来处理,容器通过Servlet接口来调用业务类。Servlet接口和Servlet容器的出现,达到HTTP服务器也业务类解耦的目的。Servlet接口和Servlet容器这一整套规范称为Servlet规范。
Tomcat按照Servlet规范的要求实现了Servlet容器,同时也具有HTTP服务器的功能。程序要实现业务功能时只需要实现一个Servlet,并将其注册到Tomcat的Servlet容器中,剩下的事情就会由Tomcat帮我们处理。
Servlet容器工作流程
为了解耦HTTP服务器不直接调用Servlet而是把请求交给Servlet容器来处理,Servlet容器是怎样工作的呢?
![](https://i-blog.csdnimg.cn/blog_migrate/b46a90b3379eaca49fbeb6c85de76500.png)
- 当客户端请求某个资源时,HTTP服务器会调用
ServletRequest
对象将请求信息封装起来,然后调用Servlet容器的service
方法。 - Servlet容器拿到请求后,根据请求的URL和Servlet的映射关系,找到对应地Servlet。
- 若此Servlet还没有被加载就会使用反射机制来创建,然后调用Servlet的
init
方法完成初始化,接收再调用Servlet的service
方法来处理请求。 - 最后将
ServletResponse
对象返回给HTTP服务器,HTTP服务器会把响应发送给客户端。
Tomcat整体架构
Tomcat需要实现两个核心功能
- 处理Socket连接,负责网络字节流和Request和Response对象的转化。
- 加载和管理Servlet,以及具体处理Request请求。
因此Tomcat设计了两个核心组件:连接器(Connector)、容器(Container)来分别做这两件事
- 连接器:负责对外交流
- 容器:负责内部处理
![](https://i-blog.csdnimg.cn/blog_migrate/4ffcc834fa38600dcf9798e99bfb30dd.png)
连接器(Coyote)
- Tomcat的连接器框架又称为Coyote,是Tomcat提供的供客户端访问的外部接口,客户端通过Coyote与服务器建立连接,发送请求并接受响应。
- Coyote封装了底层的网络通信(Socket请求以及响应处理),为Catalina容器提供了统一的接口。使Catalina容器与具体的请求协议、IO操作方式完全解耦。
- Coyote会将Socket输入转换封装为Request对象,交由Catalina容器处理,处理请求完成后Catalina通过Coyote提供的Response对象将结果写入输出流。
- Coyote作为独立模块只负责具体协议和IO相关操作,与Servlet规范实现没有实际关系,因此即便是Request和Response对象也没有实现Servlet规范对应的接口,而是在Catalina中将它们进一步封装为了ServletRequest和ServletResponse。
![](https://i-blog.csdnimg.cn/blog_migrate/b826d5e8a3c6e87cb5b855bbb2d0819a.png)
Coyote中Tomcat支持多种I/O模型
I/O模型 | 说明 |
---|---|
NIO | 非阻塞I/O,采用Java NIO类库实现。 |
NIO2 | 异步I/O,采用JDK 7最新的NIO2类库实现。 |
APR | 采用Apache可移植运行库实现,C/C++编写的本地库,需单独安装。 |
Coyote中Tomcat支持的应用层协议
应用层协议 | 说明 |
---|---|
HTTP/1.1 | 大部分Web应用采用的访问协议 |
HTTP/2 | HTTP2.0大幅提升了Web性能,是下一个的HTTP协议。 |
AJP | 用于和Web服务器集成(如Apache)以实现对静态资源的优化以及集群部署,当前支持AJP/1.3。 |
![](https://i-blog.csdnimg.cn/blog_migrate/1dbab405a296c5945f6a37d975d9f62e.png)
Tomcat为了实现支持多种I/O模型与应用层协议,一个容器可能对接多个连接器。由于单独的连接器或容器不能对外提供服务,需要将其组装起来才能工作,组装后的整体叫做Service组件。Service只是在连接和容器外层多包了一层,把它们组装在一起而已。
Tomcat内部可能有多个Service,这样设计是处于对灵活性的考虑。通过在Tomcat中配置多个Service,可以实现通过不同的端口来访问同一台机器上部署的不同应用。
连接器组件
组件 | 说明 |
---|---|
EndPoint | Coyote通信端点,通信监听的接口。 |
Processor | Coyote协议处理接口 |
ProtocolHandler | Coyote协议接口 |
Adapter | 将Request转换为ServletRequest |
![](https://i-blog.csdnimg.cn/blog_migrate/298c988832241e0e5438f74c07e9d778.png)
EndPoint
- EndPoint是Coyote通信端点(通信监听的接口),是具体Socket连接和发送的处理器,是对传输层的抽象,用来实现TCP/IP协议。
- Tomcat并没有提供EndPoint接口,而是提供了一个抽象类AbstractEndPoint。
AbstractEndpoint中定义了两个内部类Acceptor和SocketProcessor。
内部类 | 说明 |
---|---|
Acceptor | 用于监听Socket连接请求 |
SocketProcessor | 用于处理接受到的Socket请求,实现了Runnable接口,在Run方法里会调用协议处理组件Processor,来提高处理能力。 |
SocketProcessor会被提交到线程池来执行,这个线程池又成为执行器Executor。
Processor
- Processor是Coyote协议处理接口,如果说Endpoint是用来实现TCP/IP协议的,那Processor就是用来实现HTTP协议的。
- Processor接收来自EndPoint的Socket,读取字节流解析成Tomcat的Request和Response对象,再通过Adapter将其提交给容器处理。
- Processor是对应用层协议的抽象
ProtocolHandler
- ProtocolHandler是Coyote协议接口,通过Endpoint和Processor来实现针对具体协议的处理能力。
Tomcat按照协议和I/O提供了6个实现类
ProtocolHandler实现类 | 协议 | I/O |
---|---|---|
AjpNioProtocol | AJP | NIO |
AjpNio2Protocol | AJP | NIO2 |
AjpAprProtocol | AJP | APR |
Http11NioProtocol | HTTP1.1 | NIO |
Http11Nio2Protocol | HTTP1.1 | NIO2 |
Http11AprProtocol | HTTP1.1 | APR |
配置Tomcat的tomcat/conf/server.xml
时至少需要指定具体ProtocolHandler,也可以指定协议名称。
Adapter
由于协议不同,客户端发送过来的请求信息也不尽相同,Tomcat定义了自己的Request类来存放请求信息。
ProtocolHandler接口负责解析请求并生成Tomcat的Request类,但这个Request类不是标准的ServletRequest,这就意味着不能使用Tomcat Request作为参数来调用容器。
Tomcat设计者的解决方案是引用Coyote的Adapter(采用适配器设计模式),连接器通过调用Coyote的Adapter的service
方法,传入Tomcat的Request对象。通过Adapter将Request转换为ServletRequest,然后再调用容器的service
方法。
容器(Catalina)
- Tomcat本身是由一系列可配置的组件构成的Web容器,而Cateline则是Tomcat的Servlet容器。
- Catalina是Servlet容器实现,包含了所有的容器组件以及安全、会话、集群、管理等Servlet容器架构的各个方面。
- Catalina通过松耦合的方式集成Coyote,以完成按照请求协议来读写数据。同时还包含了启动入口、Shell程序等。
![](https://i-blog.csdnimg.cn/blog_migrate/4b316ded18d758ffebe972d58e72f92a.png)
Tomcat本质上是一款Servlet容器,Catalina才是Tomcat的核心,其他模块都是为Catalina提供支撑。比如:
- 通过Coyote模块提供链路通信
- 通过Jasper模块提供JSP引擎
- 通过Naming模块提供JNDI服务
- 通过Juli模块提供日志服务
![](https://i-blog.csdnimg.cn/blog_migrate/2378e84227ee365acfc7b8195887c672.png)
Catalina负责管理Server(表示整个服务器),Server下存在多个服务Service,每个Service都包含着多个Coyote实现的Connector
连接器组件和一个Container容器组件。当Tomcat启动时会初始化一个Catalina实例。
组件 | 说明 |
---|---|
Catalina | 负责解析Tomcat配置文件以此创建服务器Server组件,并根据命令来对其管理。 |
Server | 服务器,表示整个CatalinaServlet容器以及其它组件,负责组装并安装Servlet引擎、Tomcat连接器。Server通过实现LifeCycle接口来提供优雅启动和关闭的方式。 |
Service | 服务,Server内部组件,一个Server包含多个Service,它将若干个Connector组件绑定到一个Container(Engine)上。 |
Connector | 连接器,处理与客户端的通信,负责接收客户端请求,转给相关容器处理,最后向客户端响应处理结果。 |
Container | 容器,负责处理用户的Servlet请求并返回对象给Web用户 |
Container
- Tomcat设计了4种容器,分别是Engine、Host、Context、Wrapper,这4种容器不是平行关系而是父子关系。
- Tomcat通过一种分层的架构,使Servlet容器具有很好地灵活性。
![](https://i-blog.csdnimg.cn/blog_migrate/7e11972a1673f00a805360ee0a6cdedd.png)
组件 | 说明 |
---|---|
Engine | 表示整个Catalina的Servlet引擎,用来管理多个虚拟站点,一个Service最多只能有一个Engine,一个Engine可包含多个Host。 |
Host | 表示一个虚拟主机,可以给Tomcat配置多个虚拟主机地址,一个虚拟主机可包含多个Context。 |
Context | 表示一个Web应用程序,一个Web应用程序可包含多个Wrapper。 |
Wrapper | 表示一个Servlet,Wrapper作为容器最底层,不能包含子容器。 |
可通过Tomcat配置文件conf/server.xml
来理解Tomcat容器。Tomcat采用组件化设计,构成组件都是可以配置的,其中最外层是Server,其他组件按照一定的格式要求配置在顶层Server容器中。
$ vim conf/server.xml
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
</Host>
</Engine>
</Service>
</Server>
Tomcat是如何管理这些容器的呢?由于容器具有父子关系形成了一个树形结构,与之相应的设计模式是组合模式。Tomcat就是利用组合模式来管理这些容器。具体实现是所有容器组件都实现了Container接口,因此组合模式可以使得用户对单容器对象和组合具有一致性。这里的单容器对象指的是最底层的Wrapper,组合容器对象指的是上面的Context/Host/Engine。
![](https://i-blog.csdnimg.cn/blog_migrate/d3078bf503ed8e85388c9058f854b89c.png)
启动流程
加载Tomcat的配置文件,初始化容器组件,监听对应的端口号,准备接收客户端请求。
![](https://i-blog.csdnimg.cn/blog_migrate/5cf495b9c6e9ad9637c2e542fa8aa1ba.png)
Lifecycle
所有组件均存在初始化、启动、停止等生命周期方法,拥有生命周期管理的特性,Tomcat在设计时,基于生命周期管理抽象成了一个接口Lifecycle。而组件Server、Service、Container、Executor、Connector均实现了这个生命周期接口,从而具有生命周期中的核心方法:
生命周期方法 | 说明 |
---|---|
init() | 初始化组件 |
start() | 启动组件 |
stop() | 停止组件 |
destroy() | 销毁组件 |
![](https://i-blog.csdnimg.cn/blog_migrate/eaf8d35d19ee7723900d9842e98689e2.png)
组件的默认实现
![](https://i-blog.csdnimg.cn/blog_migrate/9d13342231bafc2d85f1706da2aff61e.png)
Endpoint组件
Tomcat中没有对应的Endpoint接口,但有一个抽象类AbstractEndpoint,其下有三个实现类分别是NioEndpoint、Nio2Endpoint、AprEndpoint,它们分别对应连接器Coyote支持的三种I/O模型:NIO、NIO2、APR。
ProtocolHandler
Coyote协议接口,通过封装Endpoint和Processor实现针对具体协议的处理功能。Tomcat按照协议和I/O提供了6个实现类。
协议类型 | 实现类 | 说明 |
---|---|---|
AJP | AjpNioProtocol | 采用NIO的I/O模型 |
AJP | AjpNio2Protocol | 采用NIO2的I/O模型 |
AJP | AjpAprProtocol | 采用APR的I/O模型,需依赖APR库。 |
HTTP | Http11NioProtocol | 采用NIO的I/O模型,默认使用的协议。 |
HTTP | Http11Nio2Protocol | 采用NIO2的I/O模型 |
HTTP | Http11AprProtocol | 采用APR的I/O模型,需依赖APR库。 |
请求处理
Tomcat设计这么多层次的容器,是怎样确定每个请求应该由那个Wrapper容器里的Servlet来处理呢?Tomcat使用Mapper组件来完成这个任务。
Mapper组件会将用户请求的URL定位到一个Servlet,工作原理是Mapper组件中保存了Web应用的配置信息(容器组件与访问路径之间的映射关系),比如Host容器中配置的域名、Context容器中的Web应用程序、Wrapper容器中Servlet映射的路径等,这些配置信息最终形成了一个多层次的Map。
当一个请求到来时Mapper组件通过解析请求URL中的域名和路径,再到自己保存的Map中去查找,就能定位到一个Servlet,注意一个请求URL最后只会定位到一个Wrapper容器,也就是一个Servlet。
例如:当用户访问 http://www.itcast.cn/bbs/findAll 时Tomcat是如何找到最终处理业务逻辑的Servlet的呢?
![](https://i-blog.csdnimg.cn/blog_migrate/fc76d2a0b7bf0e49513ddc404d06e12f.png)
从Tomcat设计架构层面来分析请求处理
- Connector组件Endpoint中的Acceptor监听客户端Socket连接并接收Socket。
- 将连接交给线程池Executor处理,开始执行请求响应任务。
- Processor组件读取消息报文,解析请求行、请求体、请求头、最后封装成Request对象。
- Mapper组件根据请求行的URL值和请求头的Host值,匹配由哪个Host容器、Context容器、Wrapper容器来处理请求。
- CoyoteAdaptor组件负责将Connector组件和Engine容器关联起来,把生成的Request对象和响应Response对象传递到Engine容器中,调用Pipeline。
- Engine容器的管道开始处理,管道中包含若干Valve(阀门),每个Valve负责部门处理逻辑,执行完Valve后执行基础的ValveStandardEngineValve,负责调用Host容器的Pipeline。
- Host容器的管道开始处理,流程类似,最后执行Context容器的Pipeline。
- Context容器的管道开始处理,流程类似,最后执行Wrapper容器的Pipeline。
- Wrapper容器的管道开始处理,流程类似,最后执行Wrapper容器对应的Servlet对象的处理方法。
![](https://i-blog.csdnimg.cn/blog_migrate/422f7698a7b54b4c83e912442d00fa56.png)
![](https://i-blog.csdnimg.cn/blog_migrate/dfc09f49e2e58978b8595cf3524c8cd6.png)
源码分析
Tomcat架构中各个组件各司其职,组件之间松耦合,确保了整体架构的可伸缩性和可扩展性。组件内部是如何增强其灵活性和可扩展性的呢?
Tomcat中每个Catalina组件采用了责任链模式来完成具体的请求处理,Tomcat中定义了Pipeline和Valve两个接口:
接口 | 说明 |
---|---|
Pipeline | 用于构建责任链 |
Valve | 表示责任链上的每个处理器 |
Pipeline中维护了一个基础的Valve,它始终位于Pipeline的末端(最后执行),封装了具体的请求处理和输出响应的过程。 可以直接调用addValue()
方法为Pipeline添加其他Valve,后添加的Valve位于基础Valve之前,并按照添加顺序执行。Pipeline通过获得首个Valve来启动整合链条的执行
![](https://i-blog.csdnimg.cn/blog_migrate/fdc632e1fbb032b075d8de45a42e2a0a.png)