总体架构
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,这四种容器是父子关系。
Engine 表示引擎,用来管理多个虚拟站点
Host 代表的是一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址
Context 表示一个 Web 应用程序,也是就我们写的一个项目
Wrapper 表示一个 Servlet
一个 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组件的总体结构。





