Tomcat 工作原理深层探秘 架构分析篇(一)

白菜Java自习室 涵盖核心知识

1. 整体架构

Tomcat 是全世界最著名的基于 Java 语言的轻量级应用服务器,是一款完全开源免费的 Servlet 容器实现。同时,它支持 HTML、JS 等静态资源的处理,因此又可以作为轻量级 Web 服务器使用。

Tomcat 作为轻量级 Web 服务器处理连接过来的 Socket 请求有两个功能:

  • 对外 处理连接,将收到的字节流转化为自己想要的 Request 和 Response 对象;
  • 对内 处理 Servlet,将对应的 Request 请求分发到相应的 Servlet 中;

Tomcat 其实就分为两大部分,一部分是 连接器(Connnector)处理对外连接容器(Container)管理对内的Servelet

tomcat架构图.jpg

最外层的大框就是代表一个 Tomcat 服务,一个 Tomcat 服务可以对应多个 Service。每个 Service 都有连接器和容器。我们在实际应用的时候都会修改 server.xml 配置文件,这些对应关系其实在配置文件中也能体现,将配置文件简化:

<Server port="8006" shutdown="SHUTDOWN">

  <Service name="Catalina">

    <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
    <Connector port="8010" protocol="AJP/1.3" redirectPort="8443" />

    <Engine name="Catalina" defaultHost="localhost">

      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm" resourceName="UserDatabase"/>
      </Realm>

      <Host name="localhost"  appBase="webapps">
      </Host>
      
    </Engine>   
  </Service>
</Server>
复制代码

这里我们可以看到连接器其实就是 Connector,一个 Service 中可以有多个连接器,容器其实对应的就是 Engine。Tomcat 的整体架构简单来说就是这样的对应关系,接下来我们来看连接器的整体架构和容器的整体架构。

2. 连接器组件 Coyote

Coyote 是 Tomcat 中连接器的组件名称 , 是对外的接⼝。客户端通过 Coyote 与服务器建⽴连接、发送请求并接受响应。

我们可以看到上图中连接器传给容器的是 ServletRequest 对象,而容器传给连接器的是 ServletResponse 对象,这些在网络传输过程中是肯定不行的,因为网络传输中传送的字节流。所以连接器的功能需求我们大概能总结出来以下几点:

  1. Socket 连接
  2. 读取请求网络中的字节流
  3. 根据相应的协议 (Http/AJP) 解析字节流,生成统一的 Tomcat Request 对象
  4. 将 Tomcat Request 传给容器
  5. 容器返回 Tomcat Response 对象
  6. 将 Tomcat Response 对象转换为字节流
  7. 将字节流返回给客户端

tomcat连接器.png

其实上面的细分都能总结为以下的三点:

  1. 网络通信
  2. 应用层协议的解析
  3. Tomcat 的 Request/Response 与 ServletRequest/ServletResponse 对象的转化

Tomcat 中它也用了三个类来实现上面的三个功能,分别对应如下:

tomcat连接器组件作用.png

  • EndPoint: EndPoint 是 Coyote 通信端点,即通信监听的接⼝,是具体 Socket 接收和发送处理器,是对传输层的抽象,因此 EndPoint ⽤来实现 TCP/IP 协议的。
  • Processor: Processor 是 Coyote 协议处理接⼝ ,如果说 EndPoint 是⽤来实现 TCP/IP 协议的,那么Processor ⽤来实现 HTTP 协议,Processor 接收来⾃ EndPoint 的 Socket,读取字节流解析成 Tomcat Request和 Response 对象,并通过 Adapter 将其提交到容器处理,Processor 是对应⽤层协议的抽象。
  • Adapter: 由于协议不同,客户端发过来的请求信息也不尽相同,Tomcat 定义了⾃⼰的 Request 类来封装这些请求信息。ProtocolHandler 接⼝负责解析请求并⽣成 Tomcat Request 类。但是这个 Request 对象不是标准的 ServletRequest,不能⽤ Tomcat Request 作为参数来调⽤容器。Tomcat 设计者的解决⽅案是引⼊ CoyoteAdapter,这是适配器模式的经典运⽤,连接器调⽤ CoyoteAdapter 的 Sevice ⽅法,传⼊的是 Tomcat Request 对象,CoyoteAdapter 负责将 Tomcat Request 转成 ServletRequest,再调⽤容器。

3. Servlet容器 Catalina

容器,顾名思义就是装东西的器具,那么这个 Tomcat 容器是装什么的呢?其实主要的就是装了 Servlet 的。那么容器是如何设计的呢?Tomcat 的容器设计其实是用了组合设计模式。

Catalina 是 Tomcat 的 Servlet 容器,Tomcat 本质上就是⼀款 Servlet 容器, 因为 Catalina 才是 Tomcat 的核⼼, 其他模块都是为 Catalina 提供⽀撑的。

我们往往有⼀个认识,Tomcat 就是⼀个 Catalina 的实例,因为 Catalina 是 Tomcat 的核⼼。其实,可以认为整个 Tomcat 就是⼀个 Catalina 实例,Tomcat 启动的时候会初始化这个实例,Catalina 实例通过加载 server.xml 完成其他实例的创建,创建并管理⼀个 Server,Server 创建并管理多个服务,每个服务⼜可以有多个 Connector 和⼀个 Container。

-- ⼀个 Catalina 的实例
    -- ⼀个 Server 的实例
        -- 多个 Service 的实例
            -- 每一个 Service 实例下可以有多个 Connector 实例和一个 Container 实例
复制代码

tomcat容器结构.png

  • Catalina:负责解析 Tomcat 的配置⽂件(server.xml), 以此来创建服务器 Server 组件并进⾏管理。
  • Server:服务器表示整个 Catalina Servlet 容器以及其它组件,负责组装并启动 Servlaet 引擎,Tomcat连接器。Server 通过实现 Lifecycle 接⼝,提供了⼀种优雅的启动和关闭整个系统的⽅式。
  • Service:服务是 Server 内部的组件,⼀个 Server 包含多个 Service,它将若⼲个 Connector 组件绑定到⼀个 Container。
  • Container:容器,负责处理⽤户的 Servlet 请求,并返回对象给 web ⽤户的模块。

Container 组件的具体结构

容器中有两个模块,一个是顶层模块 Engine,另一个是 Host,其实还有两个模块,一个是 Context 对应的是我们 webapp 里面的每个应用文件夹,每个文件夹就是对应一个 Context,还有一个模块 Wrapper 对应的是我们 Context 中的所有 Servlet,Wrapper 管理了访问关系与具体的 Servlet 的对应。

  • Engine:表示整个 Catalina 的 Servlet 引擎,⽤来管理多个虚拟站点,⼀个 Service 最多只能有⼀个 Engine,但是⼀个引擎可包含多个 Host。
  • Host:代表⼀个虚拟主机,或者说⼀个站点,可以给 Tomcat 配置多个虚拟主机地址,⽽⼀个虚拟主机下可包含多个 Context。
  • Context:表示⼀个 Web 应⽤程序, ⼀个 Web 应⽤可包含多个 Wrapper。
  • Wrapper:表示⼀个 Servlet,Wrapper 作为容器中的最底层,不能包含⼦容器。

4. 请求如何定位

我们就举个最简单的例子,我们本机应用上启动了一个 Tomcat,webapp 下有我们部署的一个应用 baicai。我们在浏览器上输入 http://localhost:8080/baicai/index.do 是如何找到对应 Servlet 进行处理呢?

  1. 请求发送到本机的 8080 端口,被在那里监听的 HTTP/1.1 的连接器 Connector 获得;
  2. 连接器 Connector 将字节流转换为容器所需要的 ServletRequest 对象给同级 Service 下的容器模块 Engine 进行处理;
  3. Engine 获得地址 http://localhost:8080/baicai/index 匹配他下面的 Host 主机
  4. 匹配到名为 localhost 的 Host(就算此时请求为具体的 ip,没有配置相应的 Host,也会交给名为 localhost 的 Host 进行处理,因为他是默认的主机);
  5. Host 匹配到路径为 /baicai 的 Context,即在 webapp 下面找到相应的文件夹;
  6. Context 匹配到 URL 规则为 *.do 的 servlet,对应为某个 Servlet 类;
  7. 调用其 doGet 或者 doPost 方法;
  8. Servlet 执行完以后将对象返回给 Context;
  9. Context 返回给 Host;
  10. Host 返回给 Engine;
  11. Engine 返回给连接器 Connector;
  12. 连接器 Connector 将对象解析为字节流发送给客户端;
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享