1. 程式人生 > 實用技巧 >20201214 Tomcat - 拉勾教育

20201214 Tomcat - 拉勾教育

Tomcat 系統架構與原理剖析

Tomcat 系統總體架構

Tomcat 的兩重身份:

  • Http 伺服器
    • 能夠接收並且處理 http 請求
  • Servlet 容器
    • Servlet 介面和 Servlet 容器這⼀整套內容叫作 Servlet 規範
    • Tomcat 實現了 Servlet 規範

Tomcat Servlet容器處理流程

當用戶請求某個URL資源時

  1. HTTP 伺服器會把請求資訊使用 ServletRequest 物件封裝起來
  2. 進一步去呼叫 Servlet 容器中某個具體的 Servlet
  3. 在 2)中, Servlet 容器拿到請求後,根據 URL 和 Servlet 的對映關係,找到相應的 Servlet
  4. 如果 Servlet 還沒有被載入,就用反射機制建立這個 Servlet ,並呼叫 Servlet 的 init 方法來完成初始化
  5. 接著呼叫這個具體 Servlet 的 service 方法來處理請求,請求處理結果使用 ServletResponse 物件封裝
  6. ServletResponse 物件返回給 HTTP 伺服器, HTTP 伺服器會把響應傳送給客戶端

Tomcat 系統總體架構

Tomcat 設計了兩個核心元件聯結器( Connector ) 和容器( Container ) 來完成 Tomcat 的兩大核心功能。

  • 聯結器(Coyote),負責對外交流: 處理 Socket 連線,負責網路位元組流與 Request 和 Response 物件的轉化;
  • 容器(Catalina),負責內部處理: 載入和管理 Servlet ,以及具體處理 Request 請求;

Tomcat 聯結器元件 Coyote

TCP/IP五層協議

  • 應用層: HTTP (超文字傳輸協議), HTTPS (更安全的超文字傳輸協議), FTP (檔案傳輸協議), SMTP (簡單郵件傳輸協議), DNS (域名服務), ping 命令(除錯網路環境), OSPF (開放最短路徑優先);
  • 傳輸層: UDP (使用者資料報協議), TCP (傳輸控制協議);
  • 網路層: IP (因特網協議), ICMP (控制報文協議), ARP (地址解析協議), RARP (反向地址轉換協議);

聯結器 Coyote

  • Coyote 是 Tomcat 中聯結器的元件名稱 , 是對外的介面。客戶端通過 Coyote 與伺服器建立連線、傳送請求並接受響應

  • Coyote 封裝了底層的網路通訊( Socket 請求及響應處理)

  • Coyote 使 Catalina 容器(容器元件)與具體的請求協議及 IO 操作方式完全解耦

  • Coyote 將 Socket 輸入轉換封裝為 Request 物件,進一步封裝後交由 Catalina 容器進行處理,處理請求完成後, Catalina 通過 Coyote 提供的 Response 物件將結果寫入輸出流

  • Coyote 負責的是具體協議(應用層)和 IO (傳輸層)相關內容

Tomcat Coyote 支援的 IO 模型與協議:

  • 在 8.0 之前 , Tomcat 預設採用的 I/O 方式為 BIO ,之後改為 NIO 。
  • 論 NIO 、 NIO2 還是 APR , 在效能方面均優於以往的 BIO 。 如果採用 APR , 甚至可以達到 Apache HTTP Server 的影響效能。

Coyote 的內部元件及流程

元件 作用描述
EndPoint EndPoint 是 Coyote 通訊端點,即通訊監聽的介面,是具體 Socket 接收和傳送處理器,是對傳輸層的抽象,因此 EndPoint 用來實現 TCP/IP 協議的
Processor Processor 是 Coyote 協議處理介面 ,如果說 EndPoint 是用來實現 TCP/IP 協 議的,那麼 Processor 用來實現 HTTP 協議, Processor 接收來自 EndPoint 的 Socket ,讀取位元組流解析成 Tomcat Request 和 Response 物件,並通過 Adapter 將其提交到容器處理, Processor 是對應用層協議的抽象
ProtocolHandler Coyote 協議介面, 通過 Endpoint 和 Processor , 實現針對具體協議的處理能力。 Tomcat 按照協議和 I/O 提供了 6 個實現類 : AjpNioProtocol , AjpAprProtocol , AjpNio2Protocol , Http11NioProtocol , Http11Nio2Protocol , Http11AprProtocol
Adapter 由於協議不同,客戶端發過來的請求資訊也不盡相同, Tomcat 定義了自己的 Request 類來封裝這些請求資訊。 ProtocolHandler 介面負責解析請求並生成 Tomcat Request 類。但是這個 Request 物件不是標準的 ServletRequest ,不能用 Tomcat Request 作為引數來呼叫容器。
Tomcat 設計者的解決方案是引入 CoyoteAdapter ,這是介面卡模式的經典運用,聯結器呼叫 CoyoteAdapter 的 Sevice 方法,傳入的是 Tomcat Request 物件, CoyoteAdapter 負責將 Tomcat Request 轉成 ServletRequest ,再呼叫容器

Tomcat Servlet 容器 Catalina

  • Tomcat 是⼀個由一系列可配置(conf/server.xml)的元件構成的 Web 容器,而 Catalina 是 Tomcat 的
    servlet 容器。
  • Catalina 是 Tomcat 的核心 , 其他模組都是為 Catalina 提供支撐的
  • Tomcat 就是一個 Catalina 的例項

Catalina 在容器中的地位:

Catalina 的結構:

  • 可以認為整個 Tomcat 就是一個 Catalina 例項, Tomcat 啟動的時候會初始化這個例項, Catalina 例項通過載入 server.xml 完成其他例項的建立,建立並管理一個 Server , Server 建立並管理多個服務,每個服務又可以有多個 Connector 和一個 Container 。

    • 一個 Catalina 例項(容器)
    • 一個 Server 例項(容器)
    • 多個 Service 例項(容器)
    • 每一個 Service 例項下可以有多個 Connector 例項和一個 Container 例項
  • Catalina

    負責解析 Tomcat 的配置檔案( server.xml ) , 以此來建立伺服器 Server 元件並進行管理

  • Server

    伺服器表示整個 Catalina Servlet 容器以及其它元件,負責組裝並啟動 Servlet 引擎, Tomcat 聯結器。Server 通過實現 Lifecycle 介面,提供了一種優雅的啟動和關閉整個系統的方式

  • Service

    服務是 Server 內部的元件,一個 Server 包含多個 Service 。它將若干個 Connector 元件繫結到一個 Container

  • Container

    容器,負責處理使用者的 servlet 請求,並返回物件給 web 使用者的模組

Container 元件的具體結構

Container 元件下有幾種具體的元件,分別是 Engine 、 Host 、 Context 和 Wrapper 。這 4 種元件(容器)是父子關係。 Tomcat 通過一種分層的架構,使得 Servlet 容器具有很好的靈活性。元件的配置其實就體現在conf/server.xml 中。

  • Engine

    表示整個 Catalina 的 Servlet 引擎,用來管理多個虛擬站點,一個 Service 最多隻能有一個 Engine ,但是一個引擎可包含多個 Host

  • Host

    代表一個虛擬主機,或者說一個站點,可以給 Tomcat 配置多個虛擬主機地址,而一個虛擬主機下可包含多個 Context

  • Context

    表示一個 Web 應用程式, 一個 Web 應用可包含多個 Wrapper

  • Wrapper

    表示一個 Servlet , Wrapper 作為容器中的最底層,不能包含子容器

Tomcat 伺服器核心配置詳解

  • 配置檔案 conf/server.xml
  • Server :Server 例項
    • Listener :監聽器
    • GlobalNamingResources :定義伺服器的全域性 JNDI 資源
    • Service :服務
      • Listener 生命週期監聽器
      • Executor 共享執行緒池
      • Connector 連結器
        • URIEncoding :用於指定編碼 URI 的字元編碼, Tomcat8.x 版本預設的編碼為 UTF-8 , Tomcat7.x 版本預設為 ISO-8859-1
      • Engine 容器引擎
        • Host 虛擬主機
        • Context 用於配置⼀個 Web 應用

手寫實現迷你版 Tomcat

Tomcat 原始碼構建及核心流程原始碼剖析

原始碼構建

以 Win64 位,apache-tomcat-8.5.50-src 為例:

  1. 下載原始碼,解壓後,將原始碼匯入 IDEA

  2. 新建 source 目錄,將 conf 和 webapps 目錄移動到 source 目錄下

  3. 在專案根目錄下新建 pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
    http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>apache-tomcat-8.5.50-src</artifactId>
        <name>Tomcat8.5</name>
        <version>8.5</version>
        <build>
            <!--指定源⽬錄-->
            <finalName>Tomcat8.5</finalName>
            <sourceDirectory>java</sourceDirectory>
            <resources>
                <resource>
                    <directory>java</directory>
                </resource>
            </resources>
            <plugins>
                <!--引⼊編譯外掛-->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <encoding>UTF-8</encoding>
                        <source>8</source>
                        <target>8</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
        <!--tomcat 依賴的基礎包-->
        <dependencies>
            <dependency>
                <groupId>org.easymock</groupId>
                <artifactId>easymock</artifactId>
                <version>3.4</version>
            </dependency>
            <dependency>
                <groupId>ant</groupId>
                <artifactId>ant</artifactId>
                <version>1.7.0</version>
            </dependency>
            <dependency>
                <groupId>wsdl4j</groupId>
                <artifactId>wsdl4j</artifactId>
                <version>1.6.2</version>
            </dependency>
            <dependency>
                <groupId>javax.xml</groupId>
                <artifactId>jaxrpc</artifactId>
                <version>1.1</version>
            </dependency>
            <dependency>
                <groupId>org.eclipse.jdt.core.compiler</groupId>
                <artifactId>ecj</artifactId>
                <version>4.5.1</version>
            </dependency>
            <dependency>
                <groupId>javax.xml.soap</groupId>
                <artifactId>javax.xml.soap-api</artifactId>
                <version>1.4.0</version>
            </dependency>
        </dependencies>
    </project>
    
  4. 為啟動類 Bootstrap 新增 VM Options

    -Dcatalina.home=E:\Develop\workspace\LaGou\apache-tomcat-8.5.50-src\source
    -Dcatalina.base=E:\Develop\workspace\LaGou\apache-tomcat-8.5.50-src\source
    -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
    -Djava.util.logging.config.file=E:\Develop\workspace\LaGou\apache-tomcat-8.5.50-src\source\conf\logging.properties
    
  5. 增加程式碼,org.apache.catalina.startup.ContextConfig#configureStart

    webConfig();
    
    // 增加程式碼,初始化 jasper 引擎
    context.addServletContainerInitializer(new JasperInitializer(), null);
    
  6. 解決日誌中文亂碼問題

    1. org.apache.jasper.compiler.Localizer#getMessage(java.lang.String)
    	errMsg = new String(errMsg.getBytes("ISO-8859-1"), "UTF8");
    
    2. org.apache.tomcat.util.res.StringManager#getString(java.lang.String)
    	str = new String(str.getBytes("ISO-8859-1"), "UTF8");
    

參考資料