Tomcat源码分析--转
一、架構(gòu)
下面談?wù)勎覍?duì)Tomcat架構(gòu)的理解
總體架構(gòu):
1、面向組件架構(gòu)
2、基于JMX
3、事件偵聽(tīng)
1)面向組件架構(gòu)
tomcat代碼看似很龐大,但從結(jié)構(gòu)上看卻很清晰和簡(jiǎn)單,它主要由一堆組件組成,如Server、Service、Connector等,并基于JMX管理這些組件,另外實(shí)現(xiàn)以上接口的組件也實(shí)現(xiàn)了代表生存期的接口Lifecycle,使其組件履行固定的生存期,在其整個(gè)生存期的過(guò)程中通過(guò)事件偵聽(tīng)LifecycleEvent實(shí)現(xiàn)擴(kuò)展。Tomcat的核心類圖如下所示:
1、Catalina:與開始/關(guān)閉shell腳本交互的主類,因此如果要研究啟動(dòng)和關(guān)閉的過(guò)程,就從這個(gè)類開始看起。
2、Server:是整個(gè)Tomcat組件的容器,包含一個(gè)或多個(gè)Service。
3、Service:Service是包含Connector和Container的集合,Service用適當(dāng)?shù)腃onnector接收用戶的請(qǐng)求,再發(fā)給相應(yīng)的Container來(lái)處理。
4、Connector:實(shí)現(xiàn)某一協(xié)議的連接器,如默認(rèn)的有實(shí)現(xiàn)HTTP、HTTPS、AJP協(xié)議的。
5、Container:可以理解為處理某類型請(qǐng)求的容器,處理的方式一般為把處理請(qǐng)求的處理器包裝為Valve對(duì)象,并按一定順序放入類型為Pipeline的管道里。Container有多種子類型:Engine、Host、Context和Wrapper,這幾種子類型Container依次包含,處理不同粒度的請(qǐng)求。另外Container里包含一些基礎(chǔ)服務(wù),如Loader、Manager和Realm。
6、Engine:Engine包含Host和Context,接到請(qǐng)求后仍給相應(yīng)的Host在相應(yīng)的Context里處理。
7、Host:就是我們所理解的虛擬主機(jī)。
8、Context:就是我們所部屬的具體Web應(yīng)用的上下文,每個(gè)請(qǐng)求都在是相應(yīng)的上下文里處理的
9、Wrapper:Wrapper是針對(duì)每個(gè)Servlet的Container,每個(gè)Servlet都有相應(yīng)的Wrapper來(lái)管理。
可以看出Server、Service、Connector、Container、Engine、Host、Context和Wrapper這些核心組件的作用范圍是逐層遞減,并逐層包含。
下面就是些被Container所用的基礎(chǔ)組件:
1、Loader:是被Container用來(lái)載入各種所需的Class。
2、Manager:是被Container用來(lái)管理Session池。
3、Realm:是用來(lái)處理安全里授權(quán)與認(rèn)證。
分析完核心類后,再看看Tomcat啟動(dòng)的過(guò)程,Tomcat啟動(dòng)的時(shí)序圖如下所示:
從上圖可以看出,Tomcat啟動(dòng)分為init和start兩個(gè)過(guò)程,核心組件都實(shí)現(xiàn)了Lifecycle接口,都需實(shí)現(xiàn)start方法,因此在start過(guò)程中就是從Server開始逐層調(diào)用子組件的start過(guò)程。
2)基于JMX
Tomcat會(huì)為每個(gè)組件進(jìn)行注冊(cè)過(guò)程,通過(guò)Registry管理起來(lái),而Registry是基于JMX來(lái)實(shí)現(xiàn)的,因此在看組件的init和start過(guò)程實(shí)際上就是初始化MBean和觸發(fā)MBean的start方法,會(huì)大量看到形如:
| Registry.getRegistry(null, null).invoke(mbeans, "init", false);Registry.getRegistry(null, null).invoke(mbeans, "start", false); |
這樣的代碼,這實(shí)際上就是通過(guò)JMX管理各種組件的行為和生命期。
3)事件偵聽(tīng)
各個(gè)組件在其生命期中會(huì)有各種各樣行為,而這些行為都有觸發(fā)相應(yīng)的事件,Tomcat就是通過(guò)偵聽(tīng)這些時(shí)間達(dá)到對(duì)這些行為進(jìn)行擴(kuò)展的目的。在看組件的init和start過(guò)程中會(huì)看到大量如:
| lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null); |
這樣的代碼,這就是對(duì)某一類型事件的觸發(fā),如果你想在其中加入自己的行為,就只用注冊(cè)相應(yīng)類型的事件即可。
二、一次完整請(qǐng)求的里里外外
前幾天分析了一下Tomcat的架構(gòu)和啟動(dòng)過(guò)程,今天開始研究它的運(yùn)轉(zhuǎn)機(jī)制。Tomcat最本質(zhì)就是個(gè)能運(yùn)行JSP/Servlet的Web服務(wù)器 ,因此最典型的應(yīng)用就是用戶通過(guò)瀏覽器訪問(wèn)服務(wù)器,Tomcat接收到請(qǐng)求后轉(zhuǎn)發(fā)給Servlet,由Servlet處理完后,把結(jié)果返回給客戶端。今天就專門解析一下這么一個(gè)完整的請(qǐng)求的內(nèi)部機(jī)理。
通過(guò)DEBUG,一路跟下來(lái),發(fā)現(xiàn)Tomcat處理請(qǐng)求的核心過(guò)程是以下幾點(diǎn):
1、啟動(dòng)的時(shí)候啟動(dòng)預(yù)支持協(xié)議的Endpoint,Endpoint會(huì)起專門的線程監(jiān)聽(tīng)相應(yīng)協(xié)議的請(qǐng)求,默認(rèn)的情況下,會(huì)啟動(dòng)JIoEndpoint,JIoEndpoint基于Java ServerSocket接收Http的請(qǐng)求
2、ServerSocket接收到客戶端請(qǐng)求的Socket后,一路包裝,并一路從Host一直傳遞到Wrapper,再請(qǐng)求到相應(yīng)的Servlet
下面將重點(diǎn)解析以上兩個(gè)過(guò)程。
通過(guò)以前的分析(Tomcat源碼分析一)可知道當(dāng)Tomcat啟動(dòng)的時(shí)候會(huì)啟動(dòng)Connector,此時(shí)Connector會(huì)通過(guò)ProtocolHandler把Endpoint啟動(dòng)起來(lái)。默認(rèn)情況下,Tomcat會(huì)啟動(dòng)兩種Connector,分別是Http協(xié)議和AJP協(xié)議的,依次對(duì)應(yīng)Http11Protocol和AjpProtocol,兩者都是啟動(dòng)JIoEndpoint。下面看看JIoEndpoint的start方法:
| public void start() throws Exception { // Initialize socket if not done before if (!initialized) { init(); } if (!running) { running = true; paused = false; // Create worker collection if (getExecutor() == null) { createExecutor(); } // Start acceptor threads for (int i = 0; i < acceptorThreadCount; i++) { Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i); acceptorThread.setPriority(threadPriority); acceptorThread.setDaemon(getDaemon()); acceptorThread.start(); } } } |
?
以上代碼很清晰地表示啟動(dòng)acceptorThreadCount個(gè)線程,每個(gè)線程由Acceptor代理,具體看看Acceptor的run方法:
?
| public void run() { // Loop until we receive a shutdown command while (running) { // Loop if endpoint is paused while (paused) { try { Thread.sleep(1000); } catch (InterruptedException e) { // Ignore } } // Accept the next incoming connection from the server socket try { Socket socket = serverSocketFactory.acceptSocket(serverSocket); serverSocketFactory.initSocket(socket); // Hand this socket off to an appropriate processor if (!processSocket(socket)) { // Close socket right away try { socket.close(); } catch (IOException e) { // Ignore } } }catch ( IOException x ) { if ( running ) log.error(sm.getString("endpoint.accept.fail"), x); } catch (Throwable t) { log.error(sm.getString("endpoint.accept.fail"), t); } // The processor will recycle itself when it finishes } } |
由此可得到這么一個(gè)結(jié)論:Tomcat就是通過(guò)ServerSocket監(jiān)聽(tīng)Socket的方式來(lái)接收客戶端請(qǐng)求的。具體代碼就無(wú)需我解析了,稍微了解Java net的人都能看懂以上代碼,Tomcat就是用最標(biāo)準(zhǔn)和最基礎(chǔ)的Socket調(diào)用方法來(lái)處理網(wǎng)絡(luò)請(qǐng)求的。找到處理請(qǐng)求的源頭后下面要做的是事情就簡(jiǎn)單了,打好斷點(diǎn),在瀏覽器里請(qǐng)求一個(gè)最簡(jiǎn)單的Hello world,一路debug下去。一路跟下來(lái),主流程的時(shí)序圖如下所示:
從上圖可知,以上過(guò)程可分解成以下三個(gè)最主要的核心點(diǎn):
1、基于Http1.1協(xié)議對(duì)Socket的解析和包裝
2、StandardEngineValve、StandardHostValve、StandardContextValve和StandardWrapperValve四種Valve的一路inoke。四種不同層次的Valve做了不同層次的處理和封裝
3、基于責(zé)任鏈模式ApplicationFilterChain實(shí)現(xiàn)Filter攔截和實(shí)際Servlet的請(qǐng)求
以上三個(gè)核心點(diǎn)都是內(nèi)容非常豐富的可研究點(diǎn),會(huì)在以后幾天逐一進(jìn)行剖析。
三、可攜帶狀態(tài)的線程池
最近想實(shí)現(xiàn)一個(gè)可攜帶狀態(tài)的線程池,具體需求就是池中的線程被用來(lái)處理某種信息,而此信息可視為線程所依賴的外部狀態(tài)。如果用簡(jiǎn)單的線程池來(lái)實(shí)現(xiàn),線程初始化時(shí)就得賦予某些信息,使得線程無(wú)法被再次利用。在看老版Tomcat的源碼時(shí),找到了答案,其實(shí)現(xiàn)思路主要是利用了線程的等待和喚起,HttpProcessor的實(shí)現(xiàn)正好基于此思路,時(shí)序圖如下所示:
初始化HttpProcessor線程時(shí),沒(méi)法賦予所需的Socket對(duì)象,因?yàn)槿绻诔跏蓟A段就賦予Socket會(huì)導(dǎo)致此線程沒(méi)法回收用來(lái)處理其他Socket。因此,在HttpProcessor的run階段,先把線程給wait住,具體在await方法里體現(xiàn),代碼如下所示:
| /** * Await a newly assigned Socket from our Connector, or null * if we are supposed to shut down. */ private synchronized Socket await() { // Wait for the Connector to provide a new Socket while (!available) { try { wait(); } catch (InterruptedException e) { } } // Notify the Connector that we have received this Socket Socket socket = this.socket; available = false; notifyAll(); if ((debug >= 1) && (socket != null)) log(" The incoming request has been awaited"); return (socket); } |
?
| /** * Await a newly assigned Socket from our Connector, or null * if we are supposed to shut down. */ private synchronized Socket await() { // Wait for the Connector to provide a new Socket while (!available) { try { wait(); } catch (InterruptedException e) { } } // Notify the Connector that we have received this Socket Socket socket = this.socket; available = false; notifyAll(); if ((debug >= 1) && (socket != null)) log(" The incoming request has been awaited"); return (socket); } |
當(dāng)HttpConnector調(diào)用HttpProcessor.assign(socket)方法時(shí),會(huì)給此線程賦予Socket對(duì)象,并喚起此線程,使其繼續(xù)執(zhí)行,assign方法的源碼如下所示:
| /** * Process an incoming TCP/IP connection on the specified socket. Any * exception that occurs during processing must be logged and swallowed. * NOTE: This method is called from our Connector's thread. We * must assign it to our own thread so that multiple simultaneous * requests can be handled. * * @param socket TCP socket to process */ synchronized void assign(Socket socket) { // Wait for the Processor to get the previous Socket while (available) { try { wait(); } catch (InterruptedException e) { } } // Store the newly available Socket and notify our thread this.socket = socket; available = true; notifyAll(); if ((debug >= 1) && (socket != null)) log(" An incoming request is being assigned"); } |
?
| /** * Process an incoming TCP/IP connection on the specified socket. Any * exception that occurs during processing must be logged and swallowed. * NOTE: This method is called from our Connector's thread. We * must assign it to our own thread so that multiple simultaneous * requests can be handled. * * @param socket TCP socket to process */ synchronized void assign(Socket socket) { // Wait for the Processor to get the previous Socket while (available) { try { wait(); } catch (InterruptedException e) { } } // Store the newly available Socket and notify our thread this.socket = socket; available = true; notifyAll(); if ((debug >= 1) && (socket != null)) log(" An incoming request is being assigned"); } |
線程被喚起和賦予socket對(duì)象后,繼續(xù)執(zhí)行核心的process方法,HttpProcessor.run的完整源碼如下所示:
| /** * The background thread that listens for incoming TCP/IP connections and * hands them off to an appropriate processor. */ public void run() { // Process requests until we receive a shutdown signal while (!stopped) { // Wait for the next socket to be assigned Socket socket = await(); if (socket == null) continue; // Process the request from this socket try { process(socket); } catch (Throwable t) { log("process.invoke", t); } // Finish up this request connector.recycle(this); } // Tell threadStop() we have shut ourselves down successfully synchronized (threadSync) { threadSync.notifyAll(); } } |
?
| /** * The background thread that listens for incoming TCP/IP connections and * hands them off to an appropriate processor. */ public void run() { // Process requests until we receive a shutdown signal while (!stopped) { // Wait for the next socket to be assigned Socket socket = await(); if (socket == null) continue; // Process the request from this socket try { process(socket); } catch (Throwable t) { log("process.invoke", t); } // Finish up this request connector.recycle(this); } // Tell threadStop() we have shut ourselves down successfully synchronized (threadSync) { threadSync.notifyAll(); } } |
四、Request和Response處理的全過(guò)程
從Tomcat源碼分析(二)可知,用戶的一個(gè)請(qǐng)求會(huì)經(jīng)過(guò)n個(gè)環(huán)節(jié)的處理,最后到達(dá)開發(fā)人員寫的Servlet,傳給Servlet也就是HttpServletRequest和HttpServletResponse,因此可以認(rèn)為這一路走下來(lái)無(wú)非就是把最原始的Socket包裝成Servlet里用到的HttpServletRequest和HttpServletResponse,只不過(guò)每個(gè)環(huán)節(jié)完成的包裝功能和部分不一樣而已,信息流如下圖所示:
其中,Request與Response的類圖如下所示:
org.apache.coyote.Request和org.apache.coyote.Response是Tomcat內(nèi)部使用的,不提供給開發(fā)者調(diào)用,類是final類型的。下面結(jié)合一次完整請(qǐng)求的時(shí)序圖來(lái)看看從Socket到org.apache.catalina.connector.Request的加工過(guò)程:
由上圖可見(jiàn),Request的解析和加工過(guò)程不是在一個(gè)方法里搞定,而是信息流動(dòng)過(guò)程中逐步解析的,不同層次的處理器解析不同層次的信息,在解析過(guò)程同時(shí)做了些判斷和攔截的工作,比如當(dāng)發(fā)現(xiàn)是要訪問(wèn)WEB-INF的資源,會(huì)直接返回錯(cuò)誤給客戶端等等。
來(lái)自:
http://www.uml.org.cn/j2ee/201306285.asp
?
轉(zhuǎn)載于:https://www.cnblogs.com/davidwang456/p/3821320.html
總結(jié)
以上是生活随笔為你收集整理的Tomcat源码分析--转的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 深入分析 Java I/O 的工作机制-
- 下一篇: Realm Configuration