上一篇简单介绍了什么是Tomcat以及Web应用,这次介绍Tomcat是如何实现它的功能,整体架构是什么样子的。
一、编译Tomcat源码
- 准备好JDK8以上和maven环境。
- 官网下载源码包(tomcat9.0.36版本)。
- 解压到自己指定的目录,这里我的目录是/Users/mc/Documents/workspace/tomcat9,然后进入apache-tomcat-9.0.36-src目录下新建一个home目录,将目录下的conf、 webapps文件夹移动至home目录下。
- 将项目导入IDEA,在tomcat9目录和apache-tomcat-9.0.36-src目录下分别新建pom.xml文件。
- 设置启动类为
org.apache.catalina.startup.Bootstrap
和VM options参数,启动之后访问localhost:8080
💡:上述步骤完成之后启动还会有一些问题,解决方案和具体配置信息可以参考我已经编译好的项目debug-tomcat9,这里不再赘述。
在上一篇介绍了 Tomcat 的主要功能是 HTTP 服务器 + Servlet 容器,也就是: 1、处理 Socket 连接,负责网络字节流与 Request 和 Response 对象的转化。2、加载和管理 Servlet,以及具体处理 Request 请求。 在 Tomcat 中设计了两个组件连接器 Connector 和容器 Container 来实现这两个功能
二、连接器
2.1 Connector整体结构
在Connector中有两个比较重要的组件:
/**
* Coyote protocol handler.
*/
protected final ProtocolHandler protocolHandler;
/**
* Coyote adapter.
*/
protected Adapter adapter = null;
- ProtocolHandler是用来抽象协议的接口,它的继承关系如下: 它的通用实现抽象类是AbstractProtocol,在AbstractProtocol中有三个重要的组件AbstractEndpoint、Processor、Adapter(是由Connector传入的),它们的作用分别是用来实现网络通信、应用层协议解析、将Tomcat的Request/Response与 ServletRequest/ServletResponse进行转化。ProtocolHandler的子类即是各种协议和通信模型的组合相应的具体实现类,Http2相关的组合则是由另外的接口Http2Protocol及子类实现。
- Adapter:适配器组件,由于协议不同导致请求信息不同,Tomcat定义了自己的Request类来存储请求信息。ProtocolHandler接口负责解析请求并生成Tomcat Request类。但是这个Request对象不是标准的ServletRequest,所以引入CoyoteAdapter,连接器调用 CoyoteAdapter的sevice 方法,传入Tomcat Request对象,CoyoteAdapter负责将Tomcat Request转成ServletRequest,再调用容器
@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
throws Exception {
Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);
……
……
}
这里涉及到的I/O模型有三种:
- NIO:非阻塞 I/O
- NIO.2:异步 I/O
- APR:采用Apache可移植运行库实现,是C/C++编写的本地库
支持的应用层协议:
- HTTP/1.1:大部分Web应用采用的访问协议
- HTTP/2:HTTP2.0大幅度的提升了Web性能
- AJP:用于和Web服务器集成如Apache
具体实现可能不同,但是整体的流程是一致的,AbstractEndpoint负责处理网络请求提供字节流给Processor,Processor负责解析应用层协议提供Tomcat Request对象给Adapter,Adapter负责将Tomcat Request对象转换为ServletRequest对象调用容器。
2.2 ProtocolHandler组件
-
AbstractEndpoint:是一个抽象类,对传输层协议的抽象,是具体的Socket接收和发送处理器。而AbstractEndpoint的具体子类,比如在NioEndpoint和Nio2Endpoint 中,有两个重要的子组件:Acceptor(在AbstractEndpoint中,Nio2Endpoint也有自己的实现)和SocketProcessor。Acceptor用于监听Socket连接请求,SocketProcessor用于处理接收到的Socket请求。
-
Processor:是对应用层协议的抽象,Processor接收来自Endpoint的Socket,读取字节流解析成Tomcat Request和Response对象,并通过Adapter将其提交到容器处理,它的抽象实现类AbstractProcessor对一些协议共有的属性进行封装,具体的实现有AjpProcessor、Http11Processor等实现了特定协议的解析方法和请求处理方式。
三、容器
容器就是用来装载Servlet的,在Tomcat中有四种容器:Engine、Host、Context、Wrapper,它们之间是采用组合模式的父子关系Engine(Host(Context(Wrapper)))。Engine表示引擎用来管理多个虚拟站点,一个Service最多只能有一个Engine。Host代表的是一个虚拟站点,可以给Tomcat配置多个虚拟主机地址,而一个虚拟站点下可以部署多个Web应用程序;Context表示一个Web应用程序;Wrapper表示一个Servlet;
Tomcat是用Mapper组件来确定请求由哪个Wrapper容器里的Servlet来处理请求的。Mapper组件的功能是将用户请求的URL定位到一个Servlet,它保存了容器组件与访问路径的映射关系:Host容器里配置的域名、Context容器里的Web应用路径、Wrapper容器里Servlet映射的路径,可以理解为这些配置信息就是一个多层次Map(由Mapper中的hosts、ContextVersion中的几个Wrappers等组成)。当一个请求到来时,Mapper组件通过解析请求URL里的域名和路径到Map里去查找定位到一个Servlet。