Tomcat 是一個(gè) Web 應(yīng)用服務(wù)器撬码,它是對(duì) HTTP 和 Servlet 規(guī)范的實(shí)現(xiàn)埂伦,簡單來說它做了這幾件事:處理 HTTP 協(xié)議丘跌、執(zhí)行 Servlet 和處理網(wǎng)絡(luò) I/O眶掌。這里以 6.0.53 版本為例(實(shí)現(xiàn)了 HTTP/1.1健盒、Servlet2.5)绒瘦,研究其基本結(jié)構(gòu)。
關(guān)于源碼版本扣癣,我使用的是 tomcat6惰帽,因?yàn)?7 為了重構(gòu)有太多的抽象,看著實(shí)在費(fèi)勁父虑,6 代碼雖有冗余但讀起來很直觀该酗,并且低版本也不影響理解 Tomcat 的核心流程。
體系結(jié)構(gòu)
從 server.xml
中就能夠看出 Tomcat 各組件的層次結(jié)構(gòu)士嚎,具體結(jié)構(gòu)圖如下:
- Server:代表整個(gè)容器呜魄,它可能包含一個(gè)或多個(gè) Service 和全局 JNDI 資源;
- Service:包含一個(gè)或多個(gè) Connector莱衩,這些連接器與一個(gè) Engine 相關(guān)聯(lián)耕赘;
- Engine:表示請(qǐng)求處理流水線(pipeline),它接收所有連接器的請(qǐng)求膳殷,并將響應(yīng)交給適當(dāng)?shù)倪B接器返回給客戶端;
- Host:網(wǎng)絡(luò)名稱(域名)與 Tomcat 服務(wù)器的關(guān)聯(lián)九火,默認(rèn)主機(jī)名 localhost赚窃,一個(gè) Engine 可包含多個(gè) Host;
- Connector:處理與客戶端的通信岔激,網(wǎng)絡(luò) I/O勒极;
- Context:表示一個(gè) Web 應(yīng)用程序,一個(gè) Host 包含多個(gè)上下文虑鼎。
服務(wù)器模型
服務(wù)器模型(或 I/O 模型)辱匿,描述的是 TCP 連接的處理方式键痛,以及 Socket 讀寫時(shí)線程的狀態(tài)。Java 里常用的是 BIO 和 NIO匾七,分別對(duì)應(yīng)同步阻塞和同步非阻塞兩種模型絮短,Tomcat 中的 Connector 組件就是對(duì)這兩種的封裝實(shí)現(xiàn)。
BIO - 阻塞式
Tomcat 實(shí)現(xiàn)了一個(gè)一連接一線程的簡單服務(wù)器模型昨忆,內(nèi)部采用線程池做了優(yōu)化丁频,設(shè)計(jì)如下:
- 當(dāng) Acceptor 接收到一個(gè) TCP 連接時(shí),線程池分配一個(gè)線程進(jìn)行處理邑贴;
- 線程調(diào)用 read() 方法讀取 Socket 輸入流中的字節(jié)席里,此時(shí)線程阻塞(Block),直到收到客戶端發(fā)送的數(shù)據(jù)拢驾;
- 收到數(shù)據(jù)后奖磁,進(jìn)行解碼、業(yè)務(wù)處理繁疤、編碼咖为,最后把響應(yīng)發(fā)送到客戶端,關(guān)閉連接嵌洼。
由此可以看出案疲,合理的分配線程池大小可以一定程度上提高系統(tǒng)的并發(fā)能力。
NIO - 非阻塞
BIO 的缺點(diǎn)在于不管當(dāng)前連接有沒有數(shù)據(jù)傳輸麻养,它始終阻塞占用線程池內(nèi)的一個(gè)線程褐啡,而 NIO 的處理方式是若通道無數(shù)據(jù)可讀取,此時(shí)線程不阻塞直接返回鳖昌,可用于處理其他連接备畦,提高了線程利用率。那怎么知道什么時(shí)候處理數(shù)據(jù)的讀寫呢许昨?當(dāng)通道可讀或可寫時(shí)懂盐,內(nèi)核會(huì)通知用戶程序進(jìn)行處理。
NIO 的編程比較復(fù)雜糕档,常用的是 Reactor 模式莉恼,它描述了一個(gè)利用多路復(fù)用 I/O,基于事件驅(qū)動(dòng)的服務(wù)器處理模型速那,(這里) 基于 Doug Lea 的 Scalable IO in Java 對(duì) Reactor 進(jìn)行了實(shí)現(xiàn)俐银。Tomcat 的設(shè)計(jì)略有不同,其設(shè)計(jì)如下:
- Acceptor 以阻塞模式接收 TCP 連接端仰,然后將連接注冊到 Poller 上捶惜;
- Poller 以非阻塞模式處理 SSL 握手和 HTTP 請(qǐng)求頭的讀取荔烧;
- BlockPoller 模擬阻塞處理 HTTP 請(qǐng)求體的讀取和發(fā)送響應(yīng)吱七。
值得注意的是汽久,兩類 Poller 都只是負(fù)責(zé)事件的通知,I/O 操作都是由線程池中的線程完成踊餐。那么景醇,ServerSocketChannel 為什么阻塞?為什么要模擬阻塞處理請(qǐng)求體和 Servlet 響應(yīng)市袖?相關(guān)的討論可參考:
- Why Tomcat's Non-Blocking Connector is using a blocking socket?
- Getting my head around NIO 'simulated' blocking (trying to)
Servlet API 的實(shí)現(xiàn)
Servlet 規(guī)范描述了容器如何加載和運(yùn)行 Servlet啡直,如和將請(qǐng)求映射到用戶配置的 Servlet, 如何處理請(qǐng)求和響應(yīng)等相關(guān)問題苍碟。Tomcat 主要實(shí)現(xiàn)了以下 API:
- ServletConfig :Servlet 名字和初始化參數(shù)酒觅;
- ServletContext :定義了 Web 應(yīng)用程序,Servlet 運(yùn)行的上下文微峰;
- ServletRequest :封裝客戶端請(qǐng)求舷丹;
- ServletResponse :封裝服務(wù)端響應(yīng);
- FilterChain :請(qǐng)求過濾調(diào)用鏈蜓肆;
- FilterConfig :過濾配置對(duì)象颜凯;
- RequestDispatcher :轉(zhuǎn)發(fā)請(qǐng)求,將請(qǐng)求轉(zhuǎn)發(fā)給 JSP 或另一個(gè) Servlet 處理仗扬。
其他如 Servlet症概、Filter、GenericServlet早芭、HttpServlet 接口或類則由用戶程序來實(shí)現(xiàn)彼城,更多詳細(xì)的介紹請(qǐng)參考規(guī)范內(nèi)容。
小結(jié)
Tomcat 架構(gòu)看著挺簡單但做起來難退个,難的就是把復(fù)雜的問題抽象化募壕、簡單化,體現(xiàn)到代碼上就是如何設(shè)計(jì)和抽象出類并且優(yōu)雅的組織在一起语盈,它作為一個(gè)流行的中間件舱馅,其內(nèi)部代碼的實(shí)現(xiàn)以及優(yōu)化手段,也是值得我們?nèi)パ芯亢湍7碌摹?/p>
本文由 wskwbog 創(chuàng)作刀荒,采用 知識(shí)共享4.0 許可證 - 署名-非商業(yè)性使用-禁止演繹
本站文章除注明轉(zhuǎn)載/出處外代嗤,均為本站原創(chuàng)或翻譯