Tomcat原始碼分析 (二)----- Tomcat整體架構及元件
前言
Tomcat的前身為Catalina,而Catalina又是一個輕量級的Servlet容器。在美國,catalina是一個很美的小島。所以Tomcat作者的寓意可能是想把Tomcat設計成一個優雅美麗且輕量級的web伺服器。Tomcat從4.x版本開始除了作為支援Servlet的容器外,額外加入了很多的功能,比如:jsp、el、naming等等,所以說Tomcat不僅僅是Catalina。
既然Tomcat首先是一個Servlet容器,我們應該更多的關心Servlet。
那麼,什麼是Servlet呢?
在網際網路興起之初,當時的Sun公司(後面被Oracle收購)已然看到了這次機遇,於是設計出了Applet來對Web應用的支援。不過事實卻並不是預期那麼得好,Sun悲催地發現Applet並沒有給業界帶來多大的影響。經過反思,Sun就想既然機遇出現了,市場前景也非常不錯,總不能白白放棄了呀,怎麼辦呢?於是又投入精力去搞一套規範出來,這時Servlet誕生了!
所謂Servlet,其實就是Sun為了讓Java能實現動態可互動的網頁,從而進入Web程式設計領域而制定的一套標準!
一個Servlet主要做下面三件事情:
- 建立並填充Request物件,包括:URI、引數、method、請求頭資訊、請求體資訊等
- 建立Response物件
- 執行業務邏輯,將結果通過Response的輸出流輸出到客戶端
Servlet沒有main方法,所以,如果要執行,則需要在一個容器
裡面才能執行,這個容器就是為了支援Servlet的功能而存在,Tomcat其實就是一個Servlet容器的實現。
整體架構圖
從上圖我們看出,最核心的兩個元件--聯結器(Connector)和容器(Container)起到心臟
1、Connector用於處理連線相關的事情,並提供Socket與Request和Response相關的轉化;
2、Container用於封裝和管理Servlet,以及具體處理Request請求;
一個Tomcat中只有一個Server,一個Server可以包含多個Service,一個Service只有一個Container,但是可以有多個Connectors,這是因為一個服務可以有多個連線,如同時提供Http和Https連結,也可以提供向相同協議不同埠的連線,示意圖如下(Engine、Host、Context下邊會說到):
多個 Connector 和一個 Container 就形成了一個 Service,有了 Service 就可以對外提供服務了,但是 Service 還要一個生存的環境,必須要有人能夠給她生命、掌握其生死大權,那就非 Server 莫屬了!所以整個 Tomcat 的生命週期由 Server 控制。
另外,上述的包含關係或者說是父子關係,都可以在tomcat的conf目錄下的server.xml
配置檔案中看出
上邊的配置檔案,還可以通過下邊的一張結構圖更清楚的理解:
下面我們逐一來分析各個元件的功能:
Server
表示伺服器,提供了一種優雅的方式來啟動和停止整個系統,不必單獨啟停聯結器和容器Service
表示服務,Server
可以執行多個服務。比如一個Tomcat裡面可執行訂單服務、支付服務、使用者服務等等- 每個
Service
可包含多個Connector
和一個Container
。因為每個服務允許同時支援多種協議,但是每種協議最終執行的Servlet卻是相同的 Connector
表示聯結器,比如一個服務可以同時支援AJP協議、Http協議和Https協議,每種協議可使用一種聯結器來支援Container
表示容器,可以看做Servlet容器Engine
-- 引擎Host
-- 主機Context
-- 上下文Wrapper
-- 包裝器
- Service服務之下還有各種
支撐元件
,下面簡單羅列一下這些元件Manager
-- 管理器,用於管理會話SessionLogger
-- 日誌器,用於管理日誌Loader
-- 載入器,和類載入有關,只會開放給Context所使用Pipeline
-- 管道元件,配合Valve實現過濾器功能Valve
-- 閥門元件,配合Pipeline實現過濾器功能Realm
-- 認證授權元件
除了聯結器和容器,管道元件和閥門元件也很關鍵,我們通過一張圖來看看這兩個元件
Connector和Container的微妙關係
由上述內容我們大致可以知道一個請求傳送到Tomcat之後,首先經過Service然後會交給我們的Connector,Connector用於接收請求並將接收的請求封裝為Request和Response來具體處理,Request和Response封裝完之後再交由Container進行處理,Container處理完請求之後再返回給Connector,最後在由Connector通過Socket將處理的結果返回給客戶端,這樣整個請求的就處理完了!
Connector最底層使用的是Socket來進行連線的,Request和Response是按照HTTP協議來封裝的,所以Connector同時需要實現TCP/IP協議和HTTP協議!
Connector架構分析
Connector用於接受請求並將請求封裝成Request和Response,然後交給Container進行處理,Container處理完之後在交給Connector返回給客戶端。
因此,我們可以把Connector分為四個方面進行理解:
(1)Connector如何接受請求的?
(2)如何將請求封裝成Request和Response的?
(3)封裝完之後的Request和Response如何交給Container進行處理的?
首先看一下Connector的結構圖,如下所示:
Connector就是使用ProtocolHandler來處理請求的,不同的ProtocolHandler代表不同的連線型別,比如:Http11Protocol使用的是普通Socket來連線的,Http11NioProtocol使用的是NioSocket來連線的。
其中ProtocolHandler由包含了三個部件:Endpoint、Processor、Adapter。
(1)Endpoint用來處理底層Socket的網路連線,Processor用於將Endpoint接收到的Socket封裝成Request,Adapter用於將Request交給Container進行具體的處理。
(2)Endpoint由於是處理底層的Socket網路連線,因此Endpoint是用來實現TCP/IP協議的,而Processor用來實現HTTP協議的,Adapter將請求適配到Servlet容器進行具體的處理。
(3)Endpoint的抽象實現AbstractEndpoint裡面定義的Acceptor和AsyncTimeout兩個內部類和一個Handler介面。Acceptor用於監聽請求,AsyncTimeout用於檢查非同步Request的超時,Handler用於處理接收到的Socket,在內部呼叫Processor進行處理。
Container如何處理請求的
Container處理請求是使用Pipeline-Valve管道來處理的!(Valve是閥門之意)
Pipeline-Valve是責任鏈模式,責任鏈模式是指在一個請求處理的過程中有很多處理者依次對請求進行處理,每個處理者負責做自己相應的處理,處理完之後將處理後的請求返回,再讓下一個處理著繼續處理。
但是!Pipeline-Valve使用的責任鏈模式和普通的責任鏈模式有些不同!區別主要有以下兩點:
(1)每個Pipeline都有特定的Valve,而且是在管道的最後一個執行,這個Valve叫做BaseValve,BaseValve是不可刪除的;
(2)在上層容器的管道的BaseValve中會呼叫下層容器的管道。
我們知道Container包含四個子容器,而這四個子容器對應的BaseValve分別在:StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve。
Pipeline的處理流程圖如下:
(1)Connector在接收到請求後會首先呼叫最頂層容器的Pipeline來處理,這裡的最頂層容器的Pipeline就是EnginePipeline(Engine的管道);
(2)在Engine的管道中依次會執行EngineValve1、EngineValve2等等,最後會執行StandardEngineValve,在StandardEngineValve中會呼叫Host管道,然後再依次執行Host的HostValve1、HostValve2等,最後在執行StandardHostValve,然後再依次呼叫Context的管道和Wrapper的管道,最後執行到StandardWrapperValve。
(3)當執行到StandardWrapperValve的時候,會在StandardWrapperValve中建立FilterChain,並呼叫其doFilter方法來處理請求,這個FilterChain包含著我們配置的與請求相匹配的Filter和Servlet,其doFilter方法會依次呼叫所有的Filter的doFilter方法和Servlet的service方法,這樣請求就得到了處理!
(4)當所有的Pipeline-Valve都執行完之後,並且處理完了具體的請求,這個時候就可以將返回的結果交給Connector了,Connector在通過Socket的方式將結果返回給客戶端。
總結
好了,我們已經從整體上看到了Tomcat的結構,但是對於每個元件我們並沒有詳細分析。後續章節我們會從幾個方面來學習Tomcat:
- 逐一分析各個元件
- 通過斷點的方式來跟蹤Tomcat程式碼中的一次完整請求