20201214 Tomcat - 拉勾教育
Tomcat 系統架構與原理剖析
Tomcat 系統總體架構
Tomcat 的兩重身份:
- Http 伺服器
- 能夠接收並且處理 http 請求
- Servlet 容器
- Servlet 介面和 Servlet 容器這⼀整套內容叫作 Servlet 規範
- Tomcat 實現了 Servlet 規範
Tomcat Servlet容器處理流程
當用戶請求某個URL資源時
- HTTP 伺服器會把請求資訊使用
ServletRequest
物件封裝起來 - 進一步去呼叫 Servlet 容器中某個具體的 Servlet
- 在 2)中, Servlet 容器拿到請求後,根據 URL 和 Servlet 的對映關係,找到相應的 Servlet
- 如果 Servlet 還沒有被載入,就用反射機制建立這個 Servlet ,並呼叫 Servlet 的
init
方法來完成初始化 - 接著呼叫這個具體 Servlet 的
service
方法來處理請求,請求處理結果使用ServletResponse
物件封裝 - 把
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 為例:
-
下載原始碼,解壓後,將原始碼匯入 IDEA
-
新建 source 目錄,將 conf 和 webapps 目錄移動到 source 目錄下
-
在專案根目錄下新建 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>
-
為啟動類 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
-
增加程式碼,
org.apache.catalina.startup.ContextConfig#configureStart
內webConfig(); // 增加程式碼,初始化 jasper 引擎 context.addServletContainerInitializer(new JasperInitializer(), null);
-
解決日誌中文亂碼問題
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");