Tomcat組成與工作原理 - 程式前沿

文章推薦指數: 80 %
投票人數:10人

Tomcat是什麼開源的Java Web 應用服務器,實現了Java EE(Java Platform Enterprise Edition)的部分技術規範,比如Java Servlet、Java Server ... 程式語言前端開發IOS開發Android開發雲端運算人工智慧伺服器搜尋資料庫軟體開發工具Tomcat組成與工作原理2019.10.26程式語言HOME程式語言Tomcat組成與工作原理Advertisement目錄1.Tomcat是什麼2.Servlet容器3.JSP引擎4.Connector5.Comet6.異步Servlet6.1.相關文章Tomcat是什麼開源的JavaWeb應用服務器,實現了JavaEE(JavaPlatformEnterpriseEdition)的部分技術規範,比如JavaServlet、JavaServerPage、JSTL、JavaWebSocket。

JavaEE是Sun公司為企業級應用推出的標準平臺,定義了一系列用於企業級開發的技術規範,除了上述的之外,還有EJB、JavaMail、JPA、JTA、JMS等,而這些都依賴具體容器的實現。

上圖對比了JavaEE容器的實現情況,Tomcat和Jetty都只提供了JavaWeb容器必需的Servlet和JSP規範,開發者要想實現其他的功能,需要自己依賴其他開源實現。

Glassfish是由sun公司推出,JavaEE最新規範出來之後,首先會在Glassfish上進行實現,所以是研究JavaEE最新技術的首選。

最常見的情況是使用Tomcat作為JavaWeb服務器,使用Spring提供的開箱即用的強大的功能,並依賴其他開源庫來完成負責的業務功能實現。

Servlet容器Tomcat組成如下圖:主要有Container和Connector以及相關組件構成。

Server:指的就是整個Tomcat服務器,包含多組服務,負責管理和啟動各個Service,同時監聽8005端口發過來的shutdown命令,用於關閉整個容器;Service:Tomcat封裝的、對外提供完整的、基於組件的web服務,包含Connectors、Container兩個核心組件,以及多個功能組件,各個Service之間是獨立的,但是共享同一JVM的資源;Connector:Tomcat與外部世界的連接器,監聽固定端口接收外部請求,傳遞給Container,並將Container處理的結果返回給外部;Container:Catalina,Servlet容器,內部有多層容器組成,用於管理Servlet生命週期,調用servlet相關方法。

Loader:封裝了JavaClassLoader,用於Container加載類文件;Realm:Tomcat中為web應用程序提供訪問認證和角色管理的機制;JMX:JavaSE中定義技術規範,是一個為應用程序、設備、系統等植入管理功能的框架,通過JMX可以遠程監控Tomcat的運行狀態;Jasper:Tomcat的Jsp解析引擎,用於將Jsp轉換成Java文件,並編譯成class文件。

Session:負責管理和創建session,以及Session的持久化(可自定義),支持session的集群。

Pipeline:在容器中充當管道的作用,管道中可以設置各種valve(閥門),請求和響應在經由管道中各個閥門處理,提供了一種靈活可配置的處理請求和響應的機制。

Naming:命名服務,JNDI,Java命名和目錄接口,是一組在Java應用中訪問命名和目錄服務的API。

命名服務將名稱和對象聯繫起來,使得我們可以用名稱訪問對象,目錄服務也是一種命名服務,對象不但有名稱,還有屬性。

Tomcat中可以使用JNDI定義數據源、配置信息,用於開發與部署的分離。

Container組成Engine:Servlet的頂層容器,包含一個或多個Host子容器;Host:虛擬主機,負責web應用的部署和Context的創建;Context:Web應用上下文,包含多個Wrapper,負責web配置的解析、管理所有的Web資源;Wrapper:最底層的容器,是對Servlet的封裝,負責Servlet實例的創建、執行和銷燬。

生命週期管理Tomcat為了方便管理組件和容器的生命週期,定義了從創建、啟動、到停止、銷燬共12中狀態,tomcat生命週期管理了內部狀態變化的規則控制,組件和容器只需實現相應的生命週期方法即可完成各生命週期內的操作(initInternal、startInternal、stopInternal、destroyInternal);比如執行初始化操作時,會判斷當前狀態是否New,如果不是則拋出生命週期異常;是的話則設置當前狀態為Initializing,並執行initInternal方法,由子類實現,方法執行成功則設置當前狀態為Initialized,執行失敗則設置為Failed狀態;Tomcat的生命週期管理引入了事件機制,在組件或容器的生命週期狀態發生變化時會通知事件監聽器,監聽器通過判斷事件的類型來進行相應的操作。

事件監聽器的添加可以在server.xml文件中進行配置;Tomcat各類容器的配置過程就是通過添加listener的方式來進行的,從而達到配置邏輯與容器的解耦。

如EngineConfig、HostConfig、ContextConfig。

EngineConfig:主要打印啟動和停止日誌HostConfig:主要處理部署應用,解析應用META-INF/context.xml並創建應用的ContextContextConfig:主要解析併合並web.xml,掃描應用的各類web資源(filter、servlet、listener)Tomcat的啟動過程啟動從Tomcat提供的start.sh腳本開始,shell腳本會調用Bootstrap的main方法,實際調用了Catalina相應的load、start方法。

load方法會通過Digester進行config/server.xml的解析,在解析的過程中會根據xml中的關係和配置信息來創建容器,並設置相關的屬性。

接著Catalina會調用StandardServer的init和start方法進行容器的初始化和啟動。

按照xml的配置關係,server的子元素是service,service的子元素是頂層容器Engine,每層容器有持有自己的子容器,而這些元素都實現了生命週期管理的各個方法,因此就很容易的完成整個容器的啟動、關閉等生命週期的管理。

StandardServer完成init和start方法調用後,會一直監聽來自8005端口(可配置),如果接收到shutdown命令,則會退出循環監聽,執行後續的stop和destroy方法,完成Tomcat容器的關閉。

同時也會調用JVM的Runtime.getRuntime()﴿.addShutdownHook方法,在虛擬機意外退出的時候來關閉容器。

所有容器都是繼承自ContainerBase,基類中封裝了容器中的重複工作,負責啟動容器相關的組件Loader、Logger、Manager、Cluster、Pipeline,啟動子容器(線程池併發啟動子容器,通過線程池submit多個線程,調用後返回Future對象,線程內部啟動子容器,接著調用Future對象的get方法來等待執行結果)。

List>results=newArrayList>(); for(inti=0;iresult:results){ try{ result.get(); }catch(Exceptione){ log.error(sm.getString("containerBase.threadedStartFailed"),e); fail=true; } }Web應用的部署方式注:catalina.home:安裝目錄;catalina.base:工作目錄;默認值user.dirServer.xml配置Host元素,指定appBase屬性,默認\$catalina.base/webapps/Server.xml配置Context元素,指定docBase,元素,指定web應用的路徑自定義配置:在\$catalina.base/EngineName/HostName/XXX.xml配置Context元素HostConfig監聽了StandardHost容器的事件,在start方法中解析上述配置文件:掃描appbase路徑下的所有文件夾和war包,解析各個應用的META-INF/context.xml,並創建StandardContext,並將Context加入到Host的子容器中。

解析$catalina.base/EngineName/HostName/下的所有Context配置,找到相應web應用的位置,解析各個應用的META-INF/context.xml,並創建StandardContext,並將Context加入到Host的子容器中。

注:HostConfig並沒有實際解析Context.xml,而是在ContextConfig中進行的。

HostConfig中會定期檢查watched資源文件(context.xml配置文件)ContextConfig解析context.xml順序:先解析全局的配置config/context.xml然後解析Host的默認配置EngineName/HostName/context.xml.default最後解析應用的META-INF/context.xmlContextConfig解析web.xml順序:先解析全局的配置config/web.xml然後解析Host的默認配置EngineName/HostName/web.xml.default接著解析應用的MEB-INF/web.xml掃描應用WEB-INF/lib/下的jar文件,解析其中的META-INF/web-fragment.xml最後合併xml封裝成WebXml,並設置Context注:掃描web應用和jar中的註解(Filter、Listener、Servlet)就是上述步驟中進行的。

容器的定期執行:backgroundProcess,由ContainerBase來實現的,並且只有在頂層容器中才會開啟線程。

(backgroundProcessorDelay=10標誌位來控制)Servlet生命週期Servlet是用Java編寫的服務器端程序。

其主要功能在於交互式地瀏覽和修改數據,生成動態Web內容。

請求到達server端,server根據url映射到相應的Servlet判斷Servlet實例是否存在,不存在則加載和實例化Servlet並調用init方法Server分別創建Request和Response對象,調用Servlet實例的service方法(service方法內部會根據http請求方法類型調用相應的doXXX方法)doXXX方法內為業務邏輯實現,從Request對象獲取請求參數,處理完畢之後將結果通過response對象返回給調用方當Server不再需要Servlet時(一般當Server關閉時),Server調用Servlet的destroy()方法。

loadonstartup當值為0或者大於0時,表示容器在應用啟動時就加載這個servlet;當是一個負數時或者沒有指定時,則指示容器在該servlet被選擇時才加載;正數的值越小,啟動該servlet的優先級越高;singlethreadmodel每次訪問servlet,新建servlet實體對象,但並不能保證線程安全,同時tomcat會限制servlet的實例數目最佳實踐:不要使用該模型,servlet中不要有全局變量請求處理過程根據server.xml配置的指定的connector以及端口監聽http、或者ajp請求請求到來時建立連接,解析請求參數,創建Request和Response對象,調用頂層容器pipeline的invoke方法容器之間層層調用,最終調用業務servlet的service方法Connector將response流中的數據寫到socket中Pipeline與ValvePipeline可以理解為現實中的管道,Valve為管道中的閥門,Request和Response對象在管道中經過各個閥門的處理和控制。

每個容器的管道中都有一個必不可少的basicvalve,其他的都是可選的,basicvalve在管道中最後調用,同時負責調用子容器的第一個valve。

Valve中主要的三個方法:setNext、getNext、invoke;valve之間的關係是單向鏈式結構,本身invoke方法中會調用下一個valve的invoke方法。

各層容器對應的basicvalve分別是StandardEngineValve、StandardHostValve、StandardContextValve、StandardWrapperValve。

JSP引擎JSP生命週期編譯階段:servlet容器編譯servlet源文件,生成servlet類初始化階段:加載與JSP對應的servlet類,創建其實例,並調用它的初始化方法執行階段:調用與JSP對應的servlet實例的服務方法銷燬階段:調用與JSP對應的servlet實例的銷燬方法,然後銷燬servlet實例JSP元素代碼片段:JSP聲明:JSP表達式:JSP註釋:JSP指令:JSP行為:HTML元素:html/head/body/div/p/…JSP隱式對象:request、response、out、session、application、config、pageContext、page、ExceptionJSP元素說明代碼片段:包含任意量的Java語句、變量、方法或表達式;JSP聲明:一個聲明語句可以聲明一個或多個變量、方法,供後面的Java代碼使用;JSP表達式:輸出Java表達式的值,String形式;JSP註釋:為代碼作註釋以及將某段代碼註釋掉JSP指令:用來設置與整個JSP頁面相關的屬性,定義頁面的依賴屬性,比如language、contentType、errorPage、isErrorPage、import、isThreadSafe、session等等包含其他的JSP文件、HTML文件或文本文件,是該JSP文件的一部分,會被同時編譯執行引入標籤庫的定義,可以是自定義標籤JSP行為:jsp:include、jsp:useBean、jsp:setProperty、jsp:getProperty、jsp:forwardJsp解析過程代碼片段:在_jspService()方法內直接輸出JSP聲明:在servlet類中進行輸出JSP表達式:在_jspService()方法內直接輸出JSP註釋:直接忽略,不輸出JSP指令:根據不同指令進行區分,include:對引入的文件進行解析;page相關的屬性會做為JSP的屬性,影響的是解析和請求處理時的行為JSP行為:不同的行為有不同的處理方式,jsp:useBean為例,會從pageContext根據scope的類別獲取bean對象,如果沒有會創建bean,同時存到相應scope的pageContext中HTML:在_jspService()方法內直接輸出JSP隱式對象:在_jspService()方法會進行聲明,只能在方法中使用;ConnectorHttp:HTTP是超文本傳輸協議,是客戶端瀏覽器或其他程序與Web服務器之間的應用層通信協議AJP:ApacheJServ協議(AJP)是一種二進制協議,專門代理從Web服務器到位於後端的應用程序服務器的入站請求阻塞IO非阻塞IOIO多路複用阻塞與非阻塞的區別在於進行讀操作和寫操作的系統調用時,如果此時內核態沒有數據可讀或者沒有緩衝空間可寫時,是否阻塞。

IO多路複用的好處在於可同時監聽多個socket的可讀和可寫事件,這樣就能使得應用可以同時監聽多個socket,釋放了應用線程資源。

Tomcat各類Connector對比Connector的實現模式有三種,分別是BIO、NIO、APR,可以在server.xml中指定。

JIO:用java.io編寫的TCP模塊,阻塞IONIO:用java.nio編寫的TCP模塊,非阻塞IO,(IO多路複用)APR:全稱ApachePortableRuntime,使用JNI的方式來進行讀取文件以及進行網絡傳輸ApachePortableRuntime是一個高度可移植的庫,它是ApacheHTTPServer2.x的核心。

APR具有許多用途,包括訪問高級IO功能(如sendfile,epoll和OpenSSL),操作系統級功能(隨機數生成,系統狀態等)和本地進程處理(共享內存,NT管道和Unix套接字)。

表格中字段含義說明:SupportPolling:是否支持基於IO多路複用的socket事件輪詢PollingSize:輪詢的最大連接數WaitfornextRequest:在等待下一個請求時,處理線程是否釋放,BIO是沒有釋放的,所以在keep-alive=true的情況下處理的併發連接數有限ReadRequestHeaders:由於requestheader數據較少,可以由容器提前解析完畢,不需要阻塞ReadRequestBody:讀取requestbody的數據是應用業務邏輯的事情,同時Servlet的限制,是需要阻塞讀取的WriteResponse:跟讀取requestbody的邏輯類似,同樣需要阻塞寫NIO處理相關類Acceptor線程負責接收連接,調用accept方法阻塞接收建立的連接,並對socket進行封裝成PollerEvent,指定註冊的事件為op_read,並放入到EventQueue隊列中,PollerEvent的run方法邏輯的是將Selector註冊到socket的指定事件;Poller線程從EventQueue獲取PollerEvent,並執行PollerEvent的run方法,調用Selector的select方法,如果有可讀的Socket則創建Http11NioProcessor,放入到線程池中執行;CoyoteAdapter是Connector到Container的適配器,Http11NioProcessor調用其提供的service方法,內部創建Request和Response對象,並調用最頂層容器的Pipeline中的第一個Valve的invoke方法Mapper主要處理httpurl到servlet的映射規則的解析,對外提供map方法NIOConnector主要參數CometComet是一種用於web的推送技術,能使服務器實時地將更新的信息傳送到客戶端,而無須客戶端發出請求在WebSocket出來之前,如果不適用comet,只能通過瀏覽器端輪詢Server來模擬實現服務器端推送。

Comet支持servlet異步處理IO,當連接上數據可讀時觸發事件,並異步寫數據(阻塞)Tomcat要實現Comet,只需繼承HttpServlet同時,實現CometProcessor接口Begin:新的請求連接接入調用,可進行與Request和Response相關的對象初始化操作,並保存response對象,用於後續寫入數據Read:請求連接有數據可讀時調用End:當數據可用時,如果讀取到文件結束或者response被關閉時則被調用Error:在連接上發生異常時調用,數據讀取異常、連接斷開、處理異常、socket超時Note:Read:在post請求有數據,但在begin事件中沒有處理,則會調用read,如果read沒有讀取數據,在會觸發Error回調,關閉socketEnd:當socket超時,並且response被關閉時也會調用;server被關閉時調用Error:除了socket超時不會關閉socket,其他都會關閉socketEnd和Error時間觸發時應關閉當前comet會話,即調用CometEvent的close方法Note:在事件觸發時要做好線程安全的操作異步Servlet傳統流程:首先,Servlet接收到請求之後,request數據解析;接著,調用業務接口的某些方法,以完成業務處理;最後,根據處理的結果提交響應,Servlet線程結束異步處理流程:客戶端發送一個請求Servlet容器分配一個線程來處理容器中的一個servletservlet調用request.startAsync(),保存AsyncContext,然後返回任何方式存在的容器線程都將退出,但是response仍然保持開放業務線程使用保存的AsyncContext來完成響應(線程池)客戶端收到響應Servlet線程將請求轉交給一個異步線程來執行業務處理,線程本身返回至容器,此時Servlet還沒有生成響應數據,異步線程處理完業務以後,可以直接生成響應數據(異步線程擁有ServletRequest和ServletResponse對象的引用)為什麼web應用中支持異步?推出異步,主要是針對那些比較耗時的請求:比如一次緩慢的數據庫查詢,一次外部RESTAPI調用,或者是其他一些I/O密集型操作。

這種耗時的請求會很快的耗光Servlet容器的線程池,繼而影響可擴展性。

Note:從客戶端的角度來看,request仍然像任何其他的HTTP的request-response交互一樣,只是耗費了更長的時間而已異步事件監聽onStartAsync:Request調用startAsync方法時觸發onComplete:syncContext調用complete方法時觸發onError:處理請求的過程出現異常時觸發onTimeout:socket超時觸發Note:onError/onTimeout觸發後,會緊接著回調onCompleteonComplete執行後,就不可再操作request和response相關文章你真的懂JavaScript的正則嗎?2016總結:一個應屆生的互聯網名企逐夢記TCP/IP網絡模型Lucene打分公式的推導AdvertisementAdvertisement近期文章Vue中容易被忽視的知識點2019.12.09if我是前端Leader,談談前端框架體系建設2019.12.09Spark入門(一)用SparkShell初嘗Spark滋味2019.12.08Spark入門(二)如何用Idea運行我們的Spark項目2019.12.08Spark入門(三)Spark經典的單詞統計2019.12.08Spark入門(四)Spark的map、flatMap、mapToPair2019.12.08Spark入門(五)Spark的reduce和reduceByKey2019.12.08Spark入門(六)Spark的combineByKey、sortBykey2019.12.08Spark入門(七)Spark的intersection、subtract、union和distinct2019.12.08Spark實戰尋找5億次訪問中,訪問次數最多的人2019.12.08AdvertisementAdvertisement



請為這篇文章評分?