《Tomcat內核設計剖析》讀書筆記

實習的公司做的前後端不分離的MVC模式的項目,所以Tomcat需要自己配,之前對於Tomcat瞭解甚少。所以刷這本書,熟悉熟悉,加油生活。更新中.....          2020.7.5

第1章Web服務器機制

.1 通信協議

關於網絡的相關知識,推薦《圖解HTTP》和《圖解TCP/IP》這兩本書,可以先看HTTP 的之後在看TCP/IP.

1.1.1 HТТP/HTTPS.

HTTP是一個應用層協議,它由請求和響應組成,是一個標準的B/S模型。同時,它也是一個無狀態的協議,即同一個客戶端上,此次請求與上一次請求是沒有對應關係的。

HTTPS簡單地說就是HTTP的安全版。通常,在安全性要求比較高的網站(例如銀行網站)上會看到HTTPS,它本質上也是HTTP協議,只是在HTTP增加了一個SSL或TLS協議層。

在TCP協議上加一層SSL或TLS協議,就構成HTTPS協議了。SSL/TLS協議提供了加解密的機制,所以它比HTTP明文傳輸更安全

一般HTTP的端口號爲80,而HTTPS的端口號爲443。簡單地說, SSL/TLS協議層主要的職責就是藉助下層協議的信道安全地協商出一份加密密鑰,並且用此密鑰來加密HTTP請求響應報文。它解決了以下三個安全性方面的

  • 提供驗證服務,驗證本次會話實體身份的合法性。
  • 提供加密服務,強加密機制能保證通信過程中的消息不會被破譯。
  • 提供防篡改服務,利用Hash算法對消息進行簽名,通過驗證簽名保證通信內容不被

加密解密算法與Hash算法:

  1. 對稱加密。密鑰只有一個,加密、解密都是這個密碼,加解密速度快,典型的對稱加密算法有DES、AES、RC4等。,
  2. 非對稱加密。密鑰成對出現,分別爲公鑰與私鑰,從公鑰無法推知私鑰,反之,從私鑰也不能推知公鑰。加密、解密使用不同的密鑰,公鑰加密需要私鑰解密,反之,私鑰加密需要公鑰解密。非對稱加密速度較慢,典型的非對稱加密算法有RSA, DSA, DSS等。
  3.  Hash算法,這是一種不可逆的算法,它常用於驗證數據的完整性。

 

1.1.2 HTTP請求/響應模型.

從某種意義上來說, HTTP協議永遠都由客戶端發起請求,由服務器進行響應併發送回響交報文。如果沒有客戶端進行請求或曾經請求過,那麼服務器是無法將消息推送到客戶端的。

1.1.3 解析HTTP報文

 

1.2 套接字通信

套接字通信是應用層與TCPIP協議族通信的中間抽象層,它是一組接口。應用層通過調用這些接口發送和接收數據

一般這種抽象層由操作系統提供或者由JVM自己實現。使用套接字通信可以簡單地實現應用程序在網絡上的通信。一臺機器上的應用向套接字中寫入信息,另外一臺相連的機器能讀取到。

TCP/IP協議族中有兩種套接字類型,分別是流套接字和數據報套接字,分別對應TCP協議和UDP協議。

一個TCP/IP套接字由一個互聯網地址、一個協議及一個端口號唯一確定。

套接字抽象層位於傳輸層與應用層之間。增加這一層不但很有必要而且很有用。它類似於設計模式中的門面模式,用戶沒必要知道和處理複雜的TCPIP協議族業務邏輯的細節,這時套接字就展現出它的優勢了。它把這些複雜的處理過程都隱藏在套接字接口下面,幫助用戶解析組織TCP/IP協議族報文數據,以符合TCPIP協議族,這用戶只要簡單調用接口即可實現數據的通信操作。

1.2.1 單播通信

單播通信是網絡節點之間通信方式的一種。單個網絡節點與單個網絡節點之間的通信就稱爲單播通信。它是一種一對一的模式,發送、接收信息只在兩者之間進行,同時它也是最常見的一種通信。

瀏覽網頁訪問服務器時發生的通信屬於單播通信,報文的發送與接收發生在你的電腦與網站的服務器之間。

首先,綁定本地8888端口,然後調用accept)方法進行阻塞,等待客戶端的連接,一旦有1連接到來就創建一個套接字並返回。接着,獲取輸入/輸出流,輸入流用於獲取客戶端傳輸的,數據,而輸出流則用來向客戶端響應發送數據,處理完後關閉套接字。爲了簡化代碼,這裏完成一次響應後便把ServerSocket關閉。

服務器端的8888端口已經處於監聽狀態,客戶端如果要與之通信,只須簡單地先指定服·務器端IP與端口號以實例化一個套接字,然後獲取套接字的輸出流與輸入流。輸出流用於向送數據,輸入流用於讀取服務器發送過來的數據。交互處理完後關閉套接字。

1.2.2 組播通信

所以組播通信其實是爲了彌補單播通信在某些使用場景的侷限性,它是一種一對多的傳播方式。假如某個主機結點想接收相關的信息,它只需要向路由器或交換機申請加入某組即可,路由器或交換機在接收到相關信息後就會負責向組內所有成員發送信息。組播通信有以下特點:

  1. 節省網絡資源:
  2. 有針對性地向組內成員傳播:
  3. 可以在互聯網上進行傳播協議,會導致數據不可靠

  • 組播通信中最重要的內容是如何維護路由器與主機之間的關係,其主要通過IGMP協議進行維護。它主要維護不同路由器與不同主機之間的成員關係,具體的維護方式比較複雜IGMP協議主要負責組成員的加入和退出、組內成員查詢等功能
  • 因爲組播通信相當於把主機與主機之間的通信壓力轉嫁到了路由器上面,所以要得到路由及網絡的支持才能進行組播通信。
  • 另外,你的主機必須支持組播通信,在TCPIP層面支持組播發送與接收在IP層面需要一個組播地址以指定組播,
    • 它稱爲D類地址,範圍是224.0.0~239.255.255.255這些地址根據範圍大致分爲局域網地址和因特網地址, 224.0.0.0-244.0.255用於局域網, 224.0.1.0~238.255.255.255用於因特網。
  • Tomcat默認的組播地址爲228.0.0.4,而Tomcat爲何會涉及組播通信則要歸到集羣的概念,因爲集羣涉及內存的共享問題,所以需要使用組播通信進行數據同步
  • 單播通信模式中有服務器端和客戶端之分,而組播通信模式與單播通信模式不同,每個端都是以路由器或交換機作爲中轉廣播站,任意一端向路由器或交換機發送消息,路由器或交換機負責發送給其他節點,每個節點都是等同的。

1.2.3廣播通信

它與組播通信又有不同的地方。

  1. 廣播通信的重點在於廣,它向路由器連接的所有主機都發送消息而不管主機想不想要,雖然浪費了網絡資源,但它可以不用維護路由器與主機之間的成員關係。廣播通信只能在局域網內傳播
  2. 組播通信的重點在於組,它只會向加入了組的所有成員發送消息,具有針對性強、不浪費網絡資源的特點。組播通信能在公網內傳播

1.3 服務器模型

服務器端對IO的處理模型。

1.3.1 單線程阻塞IO模型

這種模型特點在於單線程和阻塞IO:

  • 單線程即服務器端只有一個線程處理客戶端的所有請求,客戶端連接與服務器端的處理線程比是n:1,它無法同時處理多個連接,只能串行處理連接。
  • 阻塞IO是指服務器在讀寫數據時是阻塞的,讀取客戶端數據時要等待客戶端發送數據並且把操作系統內核複製到用戶進程中,這時才解除阻塞狀態。寫數據回客戶端時要等待用戶進程將數據寫入內核併發送到客戶端後才解除阻塞狀態。這種阻塞給網絡編程帶來了一個問題,服務器必須要等到客戶端成功接收才能繼續往下處理另外一個客戶端的請求,在此期間線程將無法響應任何客戶端請求。

該模型的特點:

  1. 最簡單的服務器模型,整個運行過程都只有一個線程,只能支持同時處理一個客戶端的請求(如果有多個客戶端訪問,就必須排隊等待),
  2. 服務器系統資源消耗較小
  3. 併發能力低,容錯能力差。

1.3.2 多線程阻塞IO模型

多線程阻塞IO模型的特點:

  • 支持對多個客戶端併發響應,處理能力得到大幅提高,有較大的併發量,
  • 服務器系統資源消耗量較大,而且多線程之間會產生線程切換成本,同時擁有較複雜的結構。

1.3.3 單線程非阻塞IO模型

單線程非阻塞IO模型的一個特點

在調用讀取或寫入接口後立即返回,而不會進入阻塞狀態。

非阻塞情況下套接字事件的檢測機制,一般會有如下三種檢測方式。

  • (1)應用程序遍歷套接字的事件檢測
    • 當多個客戶端向服務器請求時,服務器端會保存一個套接字連接列表中,應用層單個線程對套接字列表輪詢嘗試讀取或寫入。對於讀寫操作,如果成功讀寫到若干數據,則對讀寫到的數據進行處理;如果讀寫失敗,則下一個循環再繼續嘗試。這很好地利用了阻塞的時間,處理能力得到提升。但這種模型需要在應用程序中遍歷所有的套接字列表,同時需要處理數據的拼接,連接空閒時可能也會佔用較多CPU資源,不適合實際使用。對此改進的方法是使用事件驅動的非阻塞方式。
  • (2)內核遍歷套接字的事件檢測
    • 將套接字的遍歷工作交給了操作系統內核,對套接字遍歷的結果組織成一系列的事件列表並返回應用層處理。對於應用層,它們需要處理的對象就是這些事件,這就是其中一種事件驅動的非阻塞方式的實現。然而,它需要將所有連接的可讀寫事件列表傳到應用層,假如套接字連接數量變大,列表從內核複製到應用層也是不小的開銷。另外,當活躍連接較少時,內核與應用層之間存在很多無效的數據副本,因爲它將活躍和不活躍的連接狀態都複製到應用層中。
  • (3)內核基於回調的事件檢測
    • 內核中的套接字都對應一個回調函數,當客戶端往套接字發送數據時,內核從網卡接收數據後就會調用回調函數,在回調函數中維護事件列表,應用層獲取此事件列表即可得到所有感興趣的事件。內核基於回調的事件檢測方式有兩種。
      • 第一種是用可讀列表readList和可寫列表writeList標記讀寫事件,套接字的數量與readList和writeList兩個列表的長度一樣, readList第一個元素標爲1則表示套接字1可讀,同理, writeList第二個元素標爲1則表示套接字2可寫。多客戶端連接服務器端,當客戶端發送數據過來時,內核從網卡複製數據成功後調用回調函數將readList第一個元素置爲1,應用層發送請求讀、寫事件列表,返回內核包含了事件標識的readList和writeList事件列表,進而分表遍歷讀事件列表readList和寫事件列表writeL.ist,對置爲1的元素對應的套接字進行讀或寫操作。
      • 第二種應用層告訴內核每個套接字感興趣的事件。,當客戶端發送數據過來時,對應會有一個回調函數,內核從網卡複製數據成功後即調回調函數將套接字1作爲可讀事件eventl加入到事件列表。同樣地,內核發現網卡可寫時就將套接字2作爲可寫事件event2添加到,事件列表中。最後,應用層向內核請求讀、寫事件列表,內核將包含了eventl和event2的事件列表返回應用層,應用層通過遍歷事件列表得知套接字1有數據待讀取,於是進行讀操作,而套接字2則可以寫入數據。
      • 兩種方式由操作系統內核維護客戶端的所有連接並通過回調函數不斷更新事件列表,而應用層線程只要遍歷這些事件列表即可知道可讀取或可寫入的連接,進而對這些連接進行讀寫操作,極大提高了檢測效率, 自然處理能力也更強。
      • 對於Java來說,非阻塞IO的實現完全是基於操作系統內核的非阻塞IO,它將操作系統,的非阻塞IO的差異屏蔽並提供統一的API,讓我們不必關心操作系統。JDK會幫我們選擇非阻塞IO的實現方式,例如對於Linux系統,在支持epoll的情況下JDK會優先選擇用epoll實現Java的非阻塞IO。這種非阻塞方式的事件檢測機制就是效率最高的“內核基於回調的事件檢測”中的第二種方式。

1.3.4 多線程非阻塞1/0模型

Reactor模式

最經典的多線程非阻塞1/0模型方式是Reactor模式。

首先看單線程下的Reactor, Reactor將服務器端的整個處理過程分成若干個事件,例如分爲接收事件、讀事件、寫事件、執行事件等。

Reactor通過事件檢測機制將這些事件分發給不同處理器去處理,這些處理器包括:

  1. 接收連接的accept處理器
  2. 讀數據的read處理器
  3. 寫數據的write處理器
  4. 執行邏輯的process處理器。

在整個過程中只要有待處理的事件存在,即可以讓Reactor線程不斷往下執行,而不會阻塞在某處,所以處理效率很高。

基於單線程Reactor模型,根據實際使用場景,把它改進成多線程模式。常見的有兩種方式:

  1. 一種是在耗時的process處理器中引入多線程,如使用線程池;
  2. 直接使用多個Reactor實例,每個Reactor實例對應一個線程所有客戶端的連接接受工作統一由一個accept處理器構成,appept會將接受的客戶端連接均分配改所有的Reactor實例。

第2章Serverlt規範

2.1 Servelet接口

Servlet規範的核心接口即是Servlet接口,它是所有Servlet類必須實現的接口。在JavaServelt API中已經提供了兩個抽象類方便開發者實現Servlet類,分別是GenericServlet和HttpServlet, 

  • GenericServlet定義了一個通用的、協議無關的Servlet,
  • HttpServlet則定義了HTTP的Servlet,

Servlet接口的核心方法爲service方法,它是處理客戶端請求的方法,客戶端發起的請求會被路由到對應的Servlet對象上。前面說到的HttpServlet類的service方法把對HTTP協議的GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE等請求轉發到各自的處理方法中,即doGet, doPost, doPut, doDelete, doHead, doOptions, doTrace等方法。

一般來說,在Servlet容器中,每個Servlet類只能對應一個Servlet對象,所有請求都由同一個Servlet對象處理,但如果Servlet實現了SingleThreadModel接口則可能會在Web容器中存在多個Servlet對象對於web容器來說,實現了SingleThreadModel接口意味着一個Servlet對象對應着一個線程,所以此時Servlet的成員變量不存在線程安全問題

Servlet的生命週期主要包括加載實例化、初始化、處理客戶端請求、銷燬。

  • 加載實例化主要由Web容器完成,
  • 而其他三個階段則對應Servlet的init, service和destroy方法。

Servlet對象被創建後需要對其進行初始化操作,初始化工作可以放在以ServletConfig類型爲參數的init方法中, ServletConfig爲web.xml配置文件中配置的對應的初始化參數, 由Web容器完成web.xml配置讀取並封裝成ServletConfig對象

當Servlet初始化完成後,開始接受客戶端的請求,這些請求被封裝成ServletRequest類型的請求對象和ServletResponse類型的響應對象,

2.2 ServletRequest接口

2.3 ServletContext接口

ServletContext接口定義了運行所有Servlet的Web應用的視圖。其提供的內容包括以下幾個部分。

  1. 某個Web應用的Servlet全局存儲空間,某Web應用對應的所有Servlet共有的各種資源和功能的訪問。
  2. 獲取Web應用的部署描述配置文件的方法,例如getlnitParameter和getInitParameterNames.
  3. 添加Servlet到ServletContext裏面的方法,例如addServlet.添加Filter (過濾器)到ServletContext裏面的方法,例如addFilter.添加Listener (監聽器)到ServletContext裏面的方法,例如addListener.
  4. 全局的屬性保存和獲取功能,例如setAttribute, getAttribute, getAttributeNames和removeAttribute等。

2.4 ServletResponse接口

2.5 Filter接口

2.6會話

2.7註解 

2.8 可插拔性

 

爲了給web開發人員提供更好的可插拔性和更少的配置,可以在一個庫類或框架jar包的META-INF目錄中指定Web Fragment,

即web-fragment.xml配置文件,它可以看成Web的邏輯分區, web-fragment.xml與web.xml包含的元素基本上都相同。

部署期間, Web容器會掃描WEB-INF/lib目錄下jar包的META-INF/web-fragment.xml文件,並根據配置文件生成對應的組件。

一個Web應用可能會有一個web.xml和若干個web-fragment.xml文件, Web容器加載時會涉及順序問題。有兩種方式定義它們加載的順序:

  • 絕對順序, web.xml中的<absolute-ordering>元素用於描述加載資源的順序;
  • 相對順序, web-fragment.xml中的<ordering>元素用於描述web-fragment.xml之間的順序。

2.9 請求分發器

請求分發器負責把請求轉發給另外一個Servlet處理,或在響應中包含另外一個Servlet的輸出

RequestDispatcher接口提供了此實現機制。用戶可以通過ServletContext的getRequestDispatcher方法getNamedDispatcher方法分別以路徑或Servlet名稱作爲參數獲取對應Servlet的RequestDispatcher.

請求分發器有include和forward兩個方法。

  1. include方法是將目標Servlet包含到當前的Servlet中,主控制權在當前Servlet上。
  2. forward方法是將當前Servlet的請求轉移到目標Servlet上,主控權在目標Servlet上,當前Servlet的執行終止。

2.10 Web應用

Web應用和ServletContext接口對象是一對一的關係, ServletContext對象提供了一個Servlet和它的應用程序視圖。

Web應用可能包括Servlet, JSP、工具類、靜態文件、客戶端JavaApplet等。

Web應用結構包括

  • WEB-INF/web.xml文件
  • WEB-INF/ib/目錄下存放的所有jar包
  • WEB-INF/classes/目錄中存放的所有類
  • META-INF目錄存放的項目的一些信息
  • 以及其他根據具體目錄存放的資源。

一般WEB-INF目錄下的文件都不能由容器直接提供給客戶端訪問,但WEB-INF目錄中的內容可以通過Servlet代碼調用ServletContext的getResource和getResourceAsStream方法來訪問,並可使用RequestDispatcher調用公開這些內容。

Web容器用於加載WAR文件中Servlet的類加載器必須提供getResource方法,以加載WAR文件的JAR包中包含的任何資源。容器不允許Web應用程序覆蓋或訪問容器的實現類。一個類加載器的實現必須保證部署到容器的每個Web應用,在調用Thread.currentThread.getContextClassLoader() 時返回一個規定的ClassLoader實例。部署的每個Web應用程序的ClassLoader實例必須是一個單獨的實例。

服務器應該能在不重啓web容器的情況下更新一個Web應用程序,而更新web應用程序時Web容器應該提供可靠的方法保存這些Web應用的會話。如果調用response的sendError方法或如果Servlet產生一個異常或把錯誤傳播給容器,容器要按照Web應用部署描述文件中定義的錯誤頁面列表,根據狀態碼或異常試圖返回一個匹配的錯誤頁面。如果Web應用部署描述文件的error-page元素沒有包含exception-type或Tor-code子元素,則錯誤頁面使用默認的錯誤頁面。

歡迎頁:

Web應用的部署描述符中可以配置歡迎文件列表。當一個Web的請求URI沒有映射到一個Web資源時,可以從歡迎文件列表中按順序匹配適合的資源返回給客戶端,如歡迎頁爲index.html,則http:/ocalhost:8080/webapp請求實際變爲http:/ocalhost:8080/webapp/index.html。如果找不到對應的歡迎頁,則返回404響應。

當一個Web應用程序部署到容器中時,在Web應用程序開始處理客戶端請求之前,必須按照下述步驟順序執行:

  • ①實例化部署描述文件中<listener>元素標識的每個事件監聽器的一個實例。
  • ②對於已實例化且實現了ServletContextListener接口的監聽器實例,調用contextinitialized0)方法。
  • ③實例化部署描述文件中<filter>元素標識的每個過濾器的一個實例,並調用每個過濾器實例的init()方法。
  • ④根據load-on-startup元素值定義的順序,包含<load-on-startup>元素的<servlet>元素爲每個Servlet實例化一個實例,並調用每個Servlet實例的init()方法。對於不包含任何Servlet. Filter或Listener的Web應用,或使用註解聲明的Web應用,可以不需要web.xml部署描述符

2.11 Servlet映射

對於請求的URL, Web容器根據最長的上下文路徑匹配請求URL,然後匹配Servlet,

  1. Servlet的路徑是從整個請求URL中減去上下文和路徑參數。匹配規則如下:
  2.  Web容器嘗試匹配一個精確的Servlet路徑,如果匹配成功,則選擇該Servlet.
  3.  Web容器遞歸嘗試匹配最長的路徑前綴。
  4. 如果URL最後包含擴展名,例如jsp, Web容器將試圖匹配一個專門用於處理此擴展名的Servlet如果前三個規則都不匹配,則匹配一個默認的Servlet。

2.12部署描述文件

第3章Tomcat的啓動與關閉

3.1 Tomcat的批處理

Tomcat的啓動和關閉批處理腳本放在安裝目錄的bin子目錄裏,其中不僅包含了Windows系統的bat文件,同時還包含了UNIXLinux的shell文件。

3.1.1 startup.bat

Tomcat的啓動和關閉批處理腳本放在安裝目錄的bin子目錄裏,包含了Windows系統的bat文件,同時還包含了UNIX/Linux的shell文件。

startup.bat是一個啓動批處理腳本,它的主要功能就是找到另一個批處理腳本catalina.bat,並且執行catalina.bat,所以,將整個startup.bat的內容分成兩部分講解

  1. 設置CATALINA_HOME 的環境變量。
  2. 接收參數,在啓動時會附帶一些命令參數。

3.1.2 shutdown.bat

shutdown.bat的內容與啓動腳本startup.bat的內容基本一樣。

其執行順序也是先找到另一個批處理腳本catalina.bat的路徑,然後執行catalina.bat。不同的是,執行catalina.bat時傳入的參數不同,如啓動時傳入的參數爲start,而關閉時傳入的參數爲stop

3.1.3 catalina.bat

catalina.bat批處理腳本纔是Tomcat服務器啓動和關閉的核心腳本,它的最終目的是組合出一個最終的執行命令,組合時會涉及多個變量和組合邏輯。分成7部分進行講解。

第一部分腳本如下所示,它主要目的是在按Ctrl+C組合鍵終止程序時自動確認。當執行catalina.bat run命令時開始啓動Tomcat,然後如果按Ctrl+C組合鍵則會終止進程,而且命令窗口還會輸出“終止批處理操作嗎(YN)?"讓用戶確認,而這裏做的就是幫你自動輸入Y

第二部分腳本主要用於設置CATALINAHOME,CATALINA BASE兩個變量。

  • 第三部分主要用於嘗試尋找setenv.bat和steclasspath.bat並執行它們。,然後再將Tomcat的啓動包bootstrap.jar日誌包tomcat-juli.jar添加到CLASSPATH環境變量下
  • 第四部分是對日誌配置的設置。
  • 第五部分是執行命令前一些參數的初始化。
  • 第六部分命令主要根據不同的參數跳轉到不同的位置執行不同的命令,其實也組裝一些參數,爲下一步真正執行命令做準備。
  • 第七部分屬於命令真正執行的過程,它將前面所有腳本運行後組成一個最終的命令開始執行。

3.1.4 setclasspath.bat

在catalina.bat批處理腳本中會調setclasspath.bat批處理腳本, setclasspath.bat的職責很簡單,它只負責尋找、檢查JAVA-HOME和JRE HOME兩個環境變量。

rem In debug mode we need a real JDK (JAVA_HOME)
if ""%1"" == ""debug"" goto needJavaHome

rem Otherwise either JRE or JDK are fine
if not "%JRE_HOME%" == "" goto gotJreHome
if not "%JAVA_HOME%" == "" goto gotJavaHome
echo Neither the JAVA_HOME nor the JRE_HOME environment variable is defined
echo At least one of these environment variable is needed to run this program
goto exit

:needJavaHome
rem Check if we have a usable JDK
if "%JAVA_HOME%" == "" goto noJavaHome
if not exist "%JAVA_HOME%\bin\java.exe" goto noJavaHome
if not exist "%JAVA_HOME%\bin\jdb.exe" goto noJavaHome
if not exist "%JAVA_HOME%\bin\javac.exe" goto noJavaHome
set "JRE_HOME=%JAVA_HOME%"
goto okJava

:noJavaHome
echo The JAVA_HOME environment variable is not defined correctly.
echo It is needed to run this program in debug mode.
echo NB: JAVA_HOME should point to a JDK not a JRE.
goto exit

:gotJavaHome
rem No JRE given, use JAVA_HOME as JRE_HOME
set "JRE_HOME=%JAVA_HOME%"

3.2 Tomcat中的變量及屬性

變量及屬性的目的主要是將某些參數剝離出程序,以實現可配置性。

  • 在Tomcat中,啓動時會涉及大量環境變量、JVM系統屬性及Tomcat屬性。環境變量在操作系統中配置,也可以在批處理中添加或修改環境變量,
    • 在Tomcat程序中可通過System.getenv(name)獲取環境變量
  • JVM系統屬性可以是JVM自帶的屬性,也可以在Java執行命令中通過-D參數配置,
    • 在Tomcat程序中可通過System.getProperty(name)獲取JVM系統屬性。
  • 而Tomcat屬性主要通過catalina, properties配置文件配置,在Tomcat啓動時會加載,
    • Tomcat程序通過CatalinaProperties獲取。

3.2.1 環境變量

3.2.2 JVM系統變量

3.2.3 Tomcat屬性

第4章從整體預覽Tomcat

4.1 整體結構及組件介紹

如果將Tomcat內核高度抽象,則它可以看成由連接器(Connector)組件容器(Container)組件組成,其中:

  • Connector組件負責在服務器端處理客戶端連接,包括接收客戶端連接、接收客戶端的消息報文以及消息報文的解析等工作,
  • Container組件則負責對客戶端的請求進行邏輯處理,並把結果返回給客戶端。

4.2 請求處理的整體過程

Tomcat作爲專門處理HTTP的Web服務器,而且使用阻塞10方式接受客戶端的連接。

  • 1.當Tomcat啓動後, Connector組件的接收器(Acceptor)將會監聽是否有客戶端套接字連接並接收Socket.
  • 2一旦監聽到客戶端連接,則將連接交由線程池Executor處理,開始執行請求響應任務。
  • 3 HttpllProcessor組件負責從客戶端連接中讀取消息報文,然後開始解析HTTP的請求行、請求頭部、請求體。將解析後的報文封裝成Request對象,方便後面處理時通過Request對象獲取HTTP協議的相關值。
  • 4 Mapper組件根據HTTP協議請求行的URL屬性值和請求頭部的Host屬性值匹配由哪個Host容器、哪個Context容器、哪個Wrapper容器處理請求,這個過程其實就是根據請求從Tomcat中找到對應的Servlet,然後將路由的結果封裝到Request對象中,方便後面處理時通過Request對象選擇容器。
  • 5 CoyoteAdaptor組件負責將Connector組件和Engine容器連接起來,把前面處理過程中生成的請求對象Request和響應對象Response傳遞到Engine容器,調用它的管道
  • 6 Engine容器的管道開始處理請求,管道里包含若干閥門(Valve),每個閥門負責某些,處理邏輯。這裏用xxxValve代表某閥門,我們可以根據自己的需要往這個管道中添加多個閥門,首先執行這個xxxValve,然後才執行基礎閥門EngineValve,它會負責調用Host容器的管道。
  • 7  Host容器的管道開始處理請求,它同樣也包含若干閥門,首先執行這些閥門,然後執行基礎閥門HostValve,它繼續往下調用Context容器的管道。
  • 8 Context容器的管道開始處理請求,首先執行若干閥門,然後執行基礎閥門ContextValve,它負責調用Wrapper容器的管道。
  • 9 Wrapper容器的管道開始處理請求,首先執行若干閥門,然後執行基礎閥門WrapperValve,它會執行該Wrapper容器對應的Servlet對象的處理方法,對請求進行邏輯處理,並將結果輸出到客戶端。以上便是一個客戶端請求到達Tomcat後處理的整體流程。這裏,先對其有個整體印象,後面會深入討論更多的細節。

第5章Server組件與Service組件

Server組件和Service組件是Tomcat核心組件中最外層級的兩個組件, Server組件可以看成Tomcat的運行實例的抽象,而Service組件則可以看成Tomcat內的不同服務的抽象。

5.1 Server組件

作爲Tomcat最外層的核心組件, Server組件的作用主要有以下幾個。

  • >提供了監聽器機制,用於在Tomcat整個生命週期中對不同事件進行處理。

  • >提供了Tomcat容器全局的命名資源實現。

  • >監聽某個端口以接收SHUTDOWN命令。

5.1.1 生命週期監聽器 

爲了在Server組件的某階段執行某些邏輯,於是提供了監聽器機制在Tomcat中實現一個生命週期監聽器很簡單,只要實現LifecycleListener接口即可,在lifecycleEvent方法中對感興趣的生命週期事件進行處理

  • 1. AprLifecycleListener監聽器在Tomcat初始化前,該監聽器會嘗試初始化APR庫,假如能初始化成功,則會使用APR接受客戶端的請求並處理請求。在Tomcat銷燬後,該監聽器會做APR的清理工作。
    • APR:Apache Server經過這麼多年的發展後,將一些通用的運行時接口封裝起來提供給大家,這就是Apache Portable Run-time libraries, APR。
  • 2. JasperListener監聽器在Tomcat初始化前該監聽器會初始化Jasper組件, Jasper是Tomcat的JSP編譯器核心引擎,用於在Web應用啓動前初始化Jasper。
  • 3, JreMemoryLeakPreventionListener監聽器:該監聽器主要提供解決JRE內存泄漏和鎖文件的一種措施,該監聽器會在Tomcat初始化時使用系統類加載器先加載一些類和設置緩存屬性,以避免內存泄漏和鎖文件。
    • 以將導致被引用的類加載器無法被回收,而Tomcat在重加載一個Web應用時正是通過實例化一個新的類加載器來實現的,舊的類加載器無法被垃圾回收器回收,導致內存泄漏。
  • 4, GlobalResourcesLifecycleListener監聽器該監聽器主要負責實例化Server組件裏面JNDI資源的MBean,並提交由JMX管理。此監聽器對生命週期內的啓動事件和停止事件感興趣,它會在啓動時爲JNDI創建MBean,而在停止時銷燬MBean.
    • JNDI(Java Naming and Directory Interface)是一個應用程序設計的API,爲開發人員提供了查找和訪問各種命名和目錄服務的通用、統一的接口,類似JDBC都是構建在抽象層上。現在JNDI已經成爲J2EE的標準之一,所有的J2EE容器都必須提供一個JNDI的服務
    • MBean:描述一個可管理的資源。是一個java對象,遵循以下一些規則:1.必須是公用的,非抽象的類 2.必須有至少一個公用的構造器 3.必須實現它自己的相應的MBean接口或者實現javax.management.DynamicMBean接口4.可選的,一個MBean可以實現javax.management.NotificationBroadcaster接口MBean的類型
    • JMX(Java Management Extensions,即Java管理擴展)是一個爲應用程序、設備、系統等植入管理功能的框架。JMX可以跨越一系列異構操作系統平臺、系統體系結構網絡傳輸協議,靈活的開發無縫集成的系統、網絡和服務管理應用
  • 5. ThreadLocalLeakPreventionListener監聽器該監聽器主要解決ThreadLocal的使用可能帶來的內存泄漏問題。該監聽器會在Tomcat啓動後將監聽Web應用重加載的監聽器註冊到每個Web應用上,當Web應用重加載時.
  • 6. NamingContextListener監聽器該監聽器主要負責Server組件內全局命名資源在不同生命週期的不同操作,在Tomcat啓動時創建命名資源、綁定命名資源,在Tomcat停止前解綁命名資源、反註冊MBean.

5.1.2 全局命名資源

這個完全不懂......:)

5.1.3 監聽SHUTDOWN命令

Server會另外開放一個端口用於監聽關閉命令,這個端口默認爲8005,此端口與接收客戶端請求的端口並非同一個。客戶端傳輸的第一行如果能匹配關閉命令(默認爲SHUTDOWN),則整個Server將會關閉。

Tomcat中有兩類線程,一類是主線程,另外一類是daemon(守護)線程當Tomecat啓動時, Server將被主線程執行,其實就是完成所有的啓動工作,包括啓動接收客戶端和處理客戶端報文的線程,這些線程都是daemon(daemon守護)線程

所有啓動工作完成後,主線程將進入等待SHUTDOWN命令的環節,它將不斷嘗試讀取客戶端發送過來的消息,一旦匹配SHUTDOWN命令則跳出循環。主線程繼續往下執行Tomcat的關閉工作。最後主線程結束,整個Tomcat停止。

5.2 Service組件

Service組件是若干Connector組件和Executor組件組合而成的概念。

  • Connector組件負責監聽某端口的客戶端請求,不同的端口對應不同的Connector,
  • Executor組件在Service抽象層面提供了線程池,讓Service下的組件可以共用線程池。

默認情況下,不同的Connector組件會自己創建線程池來使用,而通過Service組件下的Executor組件則可以實現線程池共享,每個Connector組件都使用Service組件下的線程池。除了Connector組件之外,其他的組件也可以使用。

Tomcat中線程池的實現。

一個線程池的屬性起碼包含初始化線程數量、線程數組、任務隊列。

  1. 初始化線程數量指線程池初始化的線程數,
  2. 線程數組保存了線程池中的所有線程
  3. 任務隊列指添加到線程池中等待處理的所有任務。

線程池裏有兩個線程,池裏線程的工作就是不斷循環檢測任務隊列中是否有需要執行的任務,如果有,則處理並移出任務隊列。於是,可以說線程池中的所有線程的任務就是不斷檢測任務隊列並不斷執行隊列中的任務。

JUC就是java.util .concurrent工具包的簡稱。這是一個處理線程的工具包,JDK 1.5開始出現的。

  1. 創建線程的方式 --- 實現Callable接口
  2. 閉鎖
  3. 鎖分段機制
  4. volatile關鍵字與內存可見性

使用線程池時只須實例化一個對象,構造函數就會創建相應數量的線程並啓動線程,啓動的線程無限循環地檢測任務隊列,執行方法execute()僅僅把任務添加到任務隊列中。,所有任務都必須實現Runnable接口,這是線程池的任務隊列與工作線程的約定

JUC工具包作者Doug Lea當時如此規定,工作線程檢測任務隊列並調用隊列的run()方法,假如你自己重新寫一個線程池,就完全可以自己定義一個不一樣的任務接口。一個完善的線程池並不像下面的例子那樣簡單,它需要提供啓動、銷燬、增加工作線程的策略,最大工作線程數,各種狀態的獲取等操作,而且工作線程也不可能始終做無用循環,需要對任務隊列使用wait, notify優化,或者將任務隊列改用爲阻塞隊列(生產者消費者模式)

第6章Connector組件

Connector (連接器)組件是Tomcat最核心的兩個組件之一,主要的職責:

負責接收客戶端連接和客戶端請求的處理加工。每個Connector都將指定一個端口進行監聽,分別負責對請求報文解析和對響應報文組裝,解析過程生成Request對象,而組裝過程則涉及Response對象。

如果將Tomcat整體比作一個巨大的城堡,那麼Connector組件就是城堡的城門,每個人要進入城堡就必須通過城門,它爲人們進出城堡提供了通道。同時,一個城堡還可能有兩個或多個城門,每個城門代表了不同的通道。

connetor中包含Protocol組件、Mapper組件和CoyoteAdaptor組件。

  1. Protocol組件是協議的抽象,它將不同通信協議的處理進行了封裝,比如HTTP協議和AJP協議
  2. Endpoint是接收端的抽象,由於使用了不同的IO模式,因此存在多種類型的Endpoint,如
    1. BIO模式的JoEndpoint.
    2. NIO模式的NioEndpoint
    3. 本地庫IO模式的AprEndpoint.
  3. Acceptor是專門用於接收客戶端連接的接收器組件
  4.  Executor則是處理客戶端請求的線程池,Connector可能是使用了Service組件的共享線程池,也可能是Connector自己私有的線程池
  5. Processor組件是處理客戶端請求的處理器,不同的協議和不同的IO模式都有不同的處理方式,所以有不同的processor。
  6. Mapper組件可以稱爲路由器,它提供了對客戶端請求URL的映射功能,即可以通過它將請求轉發到對應的Host組件、Context組件、Wrapper組件以進行處理並響應客戶端,
  7. CoyoteAdaptor組件是一個適配器,它負責將Connector組件和Engine容器適配連接起來。
    1. 把接收到的客戶端請求報文解析生成的請求對象和響應對象Response傳遞到Engine容器。

目前Tomcat支持兩種Connector,分別是支持HTTP協議與AJP協議的Connector,用於接收和發送HTTP, AJP協議請求。

每個HTTP Connector實例對應一個端口,在同個Service實例內可以配置若干Connector實例,端口必須不同,但協議可以相同

  1. HTTP Connector包含的協議處理組件有
    • Http11Protocol (Java BIO模式)
    • Http11NioProtocol (Java NIO模式)
    • Http11AprProtocol (APR/native模式)

 Tomcat啓動時根據server.xml的<Connector>節點配置IO模式,

AJP Connector組件用於支持AJP協議通信,當我們想將Web應用中包含的靜態內容交給Apache處理時Apache與Tomcat之間的通信則使用AJP協議。

 AJPConnector包含的協議處理組件有

  • AipProtocol (Java BIO模式)
  • AjpNioProtocol (Java NIO模式)
  • AjpAprProtocol (APR/native模式)

6.1 HTTP阻塞模式協議-Httpl1Protocol

Htp11 Protocol 表示阻塞式的HTTP協議的通信,它包含從套接字連接接收、處理、響應客戶端的整個過程。它主要包含JoEndpoint組件和Http Procesor 件。

6.1.1 套接字接收終端-JloEndpoint

負責啓動某端口監聽客戶端的請求,負責接收套接字連接,負責提供一個線程池供系統處理接收到的套接字連接,負責對連接數的控制,負責安全與非安全套接字連接的實現等,

  • 1,連接數控制器--LimitLatch作爲Web服務器,

T爲了保證Web服務器不被沖垮,我們需要·採取一些保護措施,其中一種有效的方法就是採取流量控制。此處的流量更多地是指套接字的連接數,通過控制套接字連接個數來控制流量。

Tomcat的流量控制器是通過AQS併發框架來實現的

思路是先初始化同步器的最大限制值,然後每接收一個套接字就將計數變量累加1,每關閉一個套接字將計數變量減1,如此一來,一旦計數變量值大於最大限制值,則AQS機制將會將接收線程阻塞,而停止對套接字的接收,直到某些套接字處理完關閉後重新喚起接收線程往下接收套接字。

  • 2. Socket接收器-AcceptorAcceptor

主要的職責就是監聽是否有客戶端套接字連接並接收套接字,再將套接字交由任務執行器(Executor)執行。它不斷從系統底層讀取套接字,接着做盡可能少的處理,最後扔進線程池。

6.1.2 HTTP阻塞處理器-Http11Processor

6.2 HTTP非阻塞模式協議-Httpl1NioProtocol 

6.2.1非阻塞接收端-NioEndpoint

6.2.2 HTTP非阻塞處理器-Httpl1NioProcessor

6.3 HTTP APR模式協議--Httpl1AprProtoco

6.3.1 APR接收終端-AprEndpoint

6.3.2 HTTP APR處理器-Httpl1AprProcessor .

6.4 AJP Connector

6.4.1 AJP阻塞模式協議-AjpProtoco

6.4.2 AJPAPR模式協議-AjpAprProtoco 

6.5 HTTP三種模式的Connector

6.6 AJP三種模式的Connector

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章