前言
在學習內(nèi)存馬之前两入,了解tomcat的運行原理是十分有必要的。
Tomcat 整體架構(gòu)
Tomcat 結(jié)構(gòu)比較復雜敲才,我們先從重點的結(jié)構(gòu)上分別學習各個模塊裹纳。
Tomcat 中最頂層的容器是Server,一個Server包含多個Service紧武,一個Service只能有一個Container 剃氧,但可以有多個Connector。
Service主要用來提供對外服務阻星,包含兩個部分:Connector 連接器和Container 容器朋鞍。
- Connector 連接器用于處理連接相關(guān)的事情已添,并提供Socket與請求、響應之間的轉(zhuǎn)換滥酥;
- Container 容器用于封裝和管理Servlet更舞,以及具體處理Request請求;
Coyote 連接器
Coyote 是Tomcat 連接器Connector框架的名稱坎吻,封裝了底層的網(wǎng)絡通信(Socket 請求及響應處理)缆蝉,為Servlet容器Catalina提供了統(tǒng)一的接口。
Coyote 中支持多種應用層協(xié)議和I/O模型瘦真。
IO 模型 | 描述 |
---|---|
NIO | 非阻塞同步I/O刊头,采用Java NIO類庫實現(xiàn)。 |
NIO2 | 非阻塞異步I/O诸尽,采用JDK 7最新的NIO2類庫實現(xiàn)原杂。 |
APR | 采用Apache可移植運行庫實現(xiàn),是C/C++編寫的本地庫您机。需要單獨安裝APR庫穿肄。 |
應用層協(xié)議 | 描述 |
---|---|
HTTP/1.1 | 常見的Web訪問協(xié)議 |
AJP | 用于和Web服務器集成(如Apache),以實現(xiàn)對靜態(tài)資源的優(yōu)化以及集群部署际看,當前支持AJP/1.3咸产。 |
HTTP/2 | HTTP 2.0 下一代HTTP協(xié)議 |
連接器組件如下:
1、EndPoint 監(jiān)聽點
Coyote 通信端點仿村,即通信監(jiān)聽的接口锐朴,是具體Socket接收和發(fā)送處理器兴喂,用來實現(xiàn)TCP/IP協(xié)議蔼囊。
2、Processor 處理接口
Processor接收來自EndPoint的Socket衣迷,讀取字節(jié)流解析為Tomcat Request和Response對象畏鼓,實現(xiàn)HTTP協(xié)議。
3壶谒、ProtocolHandler 協(xié)議處理器
通過Endpoint 和 Processor云矫,實現(xiàn)針對具體協(xié)議的處理能力。
4汗菜、Adapter 適配器
將ProtocolHandler 協(xié)議處理器解析出的Tomcat Request對象让禀,適配成ServletRequest,再調(diào)用ServletRequest的Service方法陨界。
Container 容器
Container 用于封裝和管理Servlet巡揍,以及具體處理Request請求,在Container內(nèi)部包含了4個子容器菌瘪。
如下圖:
1腮敌、Engine
Catalina的Servlet引擎,用來管理多個虛擬的主機,每個Service一個Engine糜工,但是可以包含多個虛擬主機Host弊添。
2、Host
代表一個虛擬主機捌木,可以給Tomcat配置多個虛擬主機地址油坝,而一個虛擬主機下可包含多個Context。
3钮莲、Context
表示一個Web應用程序免钻,一個Web應用可包含多個Wrapper。
4崔拥、Wrapper
Servlet的別名极舔,是容器中的最底層。
server.xml 配置文件可以充分說明這個容器結(jié)構(gòu)链瓦。
<Server>
<Service>
<Engine>
<Host>
<Context></Context>
</Host>
</Engine>
</Service>
</Server>
Catalina 容器
在前文中多次提到Catalina容器拆魏,那么它又是做什么的呢?
Catalina負責管理Server慈俯,Server表示著整個服務器渤刃。也可以理解為一個Server就是一個Catalina。
如下圖:
組件 | 職責 |
---|---|
Catalina | 負責解析Tomcat的配置文件贴膘,以此來創(chuàng)建服務器Server組件 |
Server | 表示整個Catalina Servlet容器以及其它組件卖子,負責組裝并啟動Servlet引擎,Tomcat連接器刑峡。 |
Service | 一個Server包含多個Service洋闽。將若干個Connector 綁定到一個Container 容器(Engine)上。 |
Connector | 連接器突梦,用于處理連接相關(guān)的事情诫舅,并提供Socket與請求、響應之間的轉(zhuǎn)換宫患。 |
Container | 容器刊懈,負責處理用戶的servlet請求,并返回對象給Web用戶娃闲。 |
Tomcat 源碼安裝
下載源碼解壓后虚汛,創(chuàng)建home文件夾,并將conf皇帮、wappers目錄移到該文件夾中卷哩。
創(chuàng)建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.73-src</artifactId>
<name>Tomcat8.5</name>
<version>8.5</version>
<build>
<finalName>Tomcat8.5</finalName>
<sourceDirectory>java</sourceDirectory>
<!-- <testSourceDirectory>test</testSourceDirectory> -->
<resources>
<resource>
<directory>java</directory>
</resource>
</resources>
<!-- <testResources>
<testResource>
<directory>test</directory>
</testResource>
</testResources>-->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3</version>
<configuration>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<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>org.apache.ant</groupId>
<artifactId>ant</artifactId>
<version>1.7.0</version>
</dependency>
</dependencies>
</project>
再以maven源碼導入玲献。
配置啟動類殉疼。
-Dcatalina.home=/Users/cseroad/IdeaProjects/Tomcat_Project/apache-tomcat-8.5.73-src/home
-Dcatalina.base=/Users/cseroad/IdeaProjects/Tomcat_Project/apache-tomcat-8.5.73-src/home
-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
-Djava.util.logging.config.file=/Users/cseroad/IdeaProjects/Tomcat_Project/apache-tomcat-8.5.73-src/home/conf/logging.properties
在org.apache.catalina.startup
下梯浪,找到Bootstrap類的main方法開始啟動。
訪問tomcat如果出現(xiàn)500錯誤瓢娜,需要在org.apache.catalina.startup
包下的ContextConfig類中
configureStart方法添加
// 初始化jsp解析器
context.addServletContainerInitializer(new JasperInitializer(), null);
再次重啟即可挂洛。
請求處理
先創(chuàng)建一個Servlet并配置web.xml,然后編譯請求地址眠砾,再整個servlet_war_exploded拷貝出來虏劲。部署在Tomcat上并以源碼的形式進行追蹤分析請求過程。
web.xml
<servlet>
<servlet-name>BbsServlet</servlet-name>
<servlet-class>com.itcast.web.BbsServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BbsServlet</servlet-name>
<url-pattern>/bbs/findAll</url-pattern>
</servlet-mapping>
BbsServlet
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class BbsServlet extends HttpServlet {
public BbsServlet() {
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("GET請求");
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("post請求");
}
}
在啟動Tomcat時褒颈,Tomcat會先初始化柒巫。
然后endpoint準備完畢,等待外部的請求連接谷丸。
從org.apache.tomcat.util.net.NioEndpoint
類內(nèi)部的Acceptor類的run方法開始
處理接收到的socket對象堡掏,獲取輸入輸出流,然后注冊到Poller當中刨疼,添加到PollerEvent隊列當中泉唁。
org.apache.tomcat.util.net
的register方法。
而后Poller進行處理socket揩慕,調(diào)用org.apache.tomcat.util.net.NioEndpoint
的processKey方法處理讀寫亭畜。
里面就到org.apache.tomcat.util.net.AbstractEndpoint.processSocket
的Executor實現(xiàn)的線程池進行處理。
提交到線程池迎卤,放入線程池的 workQueue 中拴鸵。
再調(diào)用org.apache.tomcat.util.net.NioEndpoint.SocketProcessor
的doRun()方法。
org.apache.coyote.AbstractProtoco.ConnectionHandler
的process方法處理socket請求蜗搔。
該方法里面創(chuàng)建一個Http11Processor劲藐,解析socket請求。
這樣Socket中的內(nèi)容就會封裝到Request 中瘩燥。
通過getAdapter獲取合適的處理器秕重,調(diào)用service方法進行處理不同。執(zhí)行的是org.apache.catalina.connector.CoyoteAdapter
類的 service 方法。
創(chuàng)建了最終我們使用的ServletRequest和ServletResponse溶耘。
來到org.apache.catalina.connector.CoyoteAdapter
類的service方法二拐。
然后調(diào)用容器,getContainer方法會拿到service關(guān)聯(lián)的Engine凳兵。
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
先將請求傳遞到 Engine 管道中百新,傳遞到Engine Valve 這個閥中。
調(diào)用org.apache.catalina.core.StandardEngineValve
下的invoke方法庐扫。
獲取host饭望,接著invoke方法里面為
host.getPipeline().getFirst().invoke(request, response);
表示請求從 Engine Valve 傳遞到一個 Host 的管道中仗哨,在該管道中最后傳遞到 Host Valve 這個閥里。
調(diào)用org.apache.catalina.core.StandardHostValve
下的invoke方法铅辞。
獲取context厌漂,接著invoke方法里面為
context.getPipeline().getFirst().invoke(request, response);
表示從 Host Valve 傳遞到 Context 的管道中,在該管道中最后傳遞到 Context Valve 中斟珊。
調(diào)用org.apache.catalina.core.StandardContextValve
下的invoke方法苇倡。
獲取wrapper,接著invoke方法里面為
wrapper.getPipeline().getFirst().invoke(request, response);
表示請求傳遞到 Wrapper Valve 中囤踩,在這里會經(jīng)過一個過濾器鏈 Filter Chain 旨椒,最終到熟悉的Servlet中。調(diào)用org.apache.catalina.core.StandardWrapperValve
下的invoke方法堵漱。
創(chuàng)建Servlet综慎,再往下創(chuàng)建filterChain過濾器鏈。org.apache.catalina.core.StandardWrapperValve
下的invoke方法勤庐。
而后執(zhí)行過濾器鏈寥粹,org.apache.catalina.core.StandardWrapperValve
下的invoke方法。
執(zhí)行org.apache.catalina.core.ApplicationFilterChain
下的 doFilter 方法埃元。
往下調(diào)用internalDoFilter方法涝涤,實際調(diào)用的org.apache.catalina.core.ApplicationFilterChain
下的 internalDoFilter 方法阔拳。
到HttpServlet類的service方法糊肠,再調(diào)用我們編寫的doGet()方法货裹。
整個過程極為復雜精偿,畫張圖來梳理一下。
總結(jié):
endpoint收到socket請求搔预,調(diào)用handler找到Processor拯田,接著調(diào)用CoyoteAdapter船庇,將socket請求處理為Request請求。這就是tomcat連接器的工作鸭轮。接著CoyoteAdapter獲取到Engine张弛,Engine獲取到host,host獲取到Context寺董,Context獲取到Wrapper遮咖,Wrapper在反射調(diào)用就獲取到了Servlet,然后創(chuàng)建過濾器御吞、執(zhí)行過濾器鏈漓藕,再執(zhí)行Servlet享钞。
其他配置
創(chuàng)建host
在server.xml中給Host添加別名,實現(xiàn)同一個Host擁有多個網(wǎng)絡名稱暑脆。
<Host name="www.cseroad.tomcat" appBase="webapps1"
unpackWARs="true" autoDeploy="true">
<Context docBase="E:\app" path="/app" reloadable="true" ></Context>
</Host>
docBase:Web應用目錄或者War包的部署路徑添吗》菝可以是絕對路徑同窘,也可以是相對于Host appBase的相對路徑部脚。
path:Web應用的Context 路徑。
配置錯誤頁面
在web.xml中配置
<error-page>
<error-code>404</error-code>
<location>/404.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/500.html</location>
</error-page>
當出現(xiàn)404錯誤代碼丧没,就跳轉(zhuǎn)至404.html自定義頁面。
總結(jié)
針對Tomcat的源碼學習有些吃力漆际,整個過程需要結(jié)合代碼夺饲、資料不斷調(diào)試加深理解往声。
參考資料
https://blog.csdn.net/ly823260355/article/details/104181278
https://www.bilibili.com/video/BV1dJ411N7Um?p=5