总体架构
Tomcat 是一个应用服务器,那么要开发一个应用服务器,首先捋一捋它的需求,要实现那些功能。
1、 首先可以和客户端建立连接,并且能够处理客户端的连接
2、 其次还要解析处理我们写的 Servlet
3、 最后能够根据客户端的请求找到相应的 Servlet。
在 Tomcat 中将这些需求分为了两大功能
- 处理 Socket 连接,并将网络字节流转换成 Request 对象和 Response 对象
- 解析、加载和管理 Servlet,处理请求并返回响应数据
Tomcat 将这两大功能,设计成了两个主要的组件
- 连接器(Connector)
- 容器(Container)
来看一下 Tomcat 的总体架构
上图中是 tomcat 的整体架构,一个 Tomcat 代表一个 Server,一个 Server 下包含对个 Service,每个 Service 下包含多个连接器和一个容器。
Service 本身没有什么重要的作用,它只是把连接器和容器组装在一起了,但是 Tomcat 可以同时设置多个 Service,也就可以部署多个服务。比如有两个相同的项目,可以把这两个项目放到两个 Service 里,那这两个相同的项目就可以在一个 Tomcat 里运行了,不用担心冲突的问题。
这些配置可以在 conf/server.xml 中查看。
接下来重点关注一下连接器和容器,这是 Tomcat 工作的核心。
连接器(Connector)
在分析连接器之前,先了解一下 Tomcat 支持的 I/O 模型和应用层协议。
I/O 模型:
- NIO:非阻塞 I/O, Java NIO 类库实现。
- NIO2:异步 I/O, JDK 7 最新的 NIO2 类库实现。
- APR: Apache 可移植运行库实现,是 C/C++ 编写的本地库。
应用层协议:
- HTTP/1.1
- AJP:用于和 Web 服务器集成(如 Apache)。
- HTTP/2:HTTP 2.0 大幅度的提升了 Web 性能。
Service 中存在多个连接器就是为了支持 Tomcat 的多个 I/O 模型和应用层协议。
OK,现在来分析连接器。
首先可以先看一看 Tomcat 中连接器的配置
<Connector port="24100" protocol="HTTP/1.1"
connectionTimeout="20000"
compression="on"
compressionMinSize="2048"
noCompressionUserAgents="gozilla, traviata"
compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain,image/jpeg,image/gif" />
连接器中配置了监听的端口和使用的应用层协议等信息。
在上面说了,连接器的主要作用是处理 Socket 连接,并将网络字节流转换成 Request 对象和 Response 对象。那么我可以再试着捋一捋连接器的需求
1、 监听端口
2、 建立连接
3、 获取客户端传输的字节流
4、 根据应用层协议解析字节流,将解析的数据交给容器处理
5、 容器返回响应
6、 将响应转换成字节流返回给客户端
根据以上的需求,Tomcat 将整个连接器分为了三部分
Tomcat 将这三个功能分成了三个模块:Endpoint、Processor 和 Adapter,三个模块只通过抽象接口进行交互,封装了变化,降低了耦合度。
三个模块的处理逻辑为:
1、Endpoint 接收字节流,并交给 Processor。
2、Processor 拿到字节流后,将字节流解析成 Tomcat Request 对象并交给 Adapter。
3、Adapter 拿到 Tomcat Request 对象再解析成 ServletRequest 交给容器。
Tomcat 并没有直接将字节流解析成 ServletRequest 而是先解析成了 Tomcat Request,再通过 Adapter 进行转换,这样做的好处可以使连接器和容器接偶,我们可以自己实现 Adapter 的功能,来对接我们自己实现的类似容器的功能。
由于 Tocmat 支持多种 I/O 模型和应用层协议,并且这些 I/O 模型和应用层协议可以自由组合,比如 NIO + HTTP 或者 NIO2 + AJP。Tomcat 设计了一个 ProtocolHandler,将网络通信和解析应用层协议放到了一起,来封装这两点的变化。
来看一下连接器的结构图
来看一下连接器各个组件的代码结构
Endpoint
Endpoint 不是一个接口,只有对应的实现类 AbstractEndpoint
AbstractEndpoint 的实现类中包含了 Tomcat 支持的 I/O 模型。
Processor
Processor 的实现类是包含了 Tomcat 支持的所有应用协议。
ProtocolHandler
ProtocolHandler 的实现类里包含了每一种 I/O 模型和协议的组合。
Adapter
Adapter 只有一个实现类 CoyoteAdapter,CoyoteAdapter 是一个典型的适配器模式的使用,ProtocolHandler 中将不同的 IO 模式和不同的应用层协议通过 Endpoint 和 Processor 封装成 Tomcat Request,这个 Request 在 Adapter 中转换成标准的 ServletRequest。这其实也是一个扩展点,我们可以实现自己的 Adapter,拿到 Request 进行自己的业务处理,甚至可以不用 Servlet 那一套,自己定义一套新的应用处理模式。
容器(Container)
容器的作用是解析、加载和管理 Servlet,处理请求并返回响应数据。在 Tomcat 中设计了四种容器 Engine、Host、Context 和 Wrapper,这四种容器是父子关系。
一个 Service 最多只能有一个 Engine,一个 Engine 中可以包含多个 Host,一个 Host 中可以包含多个个 Context,一个 Context 可以包含多个 Wrapper
看一下它的结构图
可以结合 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 容器是怎么确定请求的是那个 Servlet 的呢?
通过一个例子来说明一下,图片是盗来的,哈哈哈哈。
上面这个例子中,要访问 http://user.shopping.com:8080/order/buy。Tomcat通过连接器解析数据后,交给容器,
1、 根据域名找到对应的 Host,也就是在 conf/server.xml 中配置的和 Host 的 name 相同的 Host
2、根据 URL 找到 Context
3、根据 URL 找到 Wrapper(Servlet)
当连接器将数据给到容器后,并不是直到找到 Servlet 才开始处理数据,容器的每一层都会对数据进行一些处理。Tomcat 用了一个叫做 Pipeline-Valve 管道的方式来对数据进行处理。
Pipeline-Valve 管道
Pipeline-Valve 管道是一种责任链模式,其中 Valve 表示一个处理点,Pipeline 中包含多个 Valve,每个容器中包含一个 Pipeline,每个容器中的 Pipeline 必须包含一个 BasicValve,处于调用的最末端,负责调用下个容器的 Value。
用一张图来解释一下
Wrapper 容器的最后一个 Valve 会创建一个 Filter 链,并调用 doFilter 方法,最终会调到 Servlet 的 service 方法。
来看一下容器的代码结构
Tomcat 设计了一个顶层的容器接口
public interface Container extends Lifecycle {
public Container getParent();
public void setParent(Container container);
public void addChild(Container child);
}
各个容器继承了这个顶层的容器
Container 中定义了操作父容器和子容器的方法,很明显的组合模式。
再来看看每个实现类的结构
每个类同时又继承了一个 Container 的实现抽象类ContainerBase ,看一下这个类
public abstract class ContainerBase extends LifecycleMBeanBase
implements Container {
protected final Pipeline pipeline = new StandardPipeline(this);
}
在 ContainerBase 中有Pipeline 的属性,这就是 Pipeline-Valve 管道。
OK,最后结合Java类来看看Tomcat组件的总体结构。
|