1、架構shell
下面談談我對Tomcat架構的理解apache
整體架構:瀏覽器
一、面向組件架構tomcat
二、基於JMX安全
三、事件偵聽服務器
1)面向組件架構網絡
tomcat代碼看似很龐大,但從結構上看卻很清晰和簡單,它主要由一堆組件組成,如Server、Service、Connector等,並基於JMX管理這些組件,另外實現以上接口的組件也實現了表明生存期的接口Lifecycle,使其組件履行固定的生存期,在其整個生存期的過程當中經過事件偵聽LifecycleEvent實現擴展。Tomcat的核心類圖以下所示:架構
一、Catalina:與開始/關閉shell腳本交互的主類,所以若是要研究啓動和關閉的過程,就從這個類開始看起。app
二、Server:是整個Tomcat組件的容器,包含一個或多個Service。socket
三、Service:Service是包含Connector和Container的集合,Service用適當的Connector接收用戶的請求,再發給相應的Container來處理。
四、Connector:實現某一協議的鏈接器,如默認的有實現HTTP、HTTPS、AJP協議的。
五、Container:能夠理解爲處理某類型請求的容器,處理的方式通常爲把處理請求的處理器包裝爲Valve對象,並按必定順序放入類型爲Pipeline的管道里。Container有多種子類型:Engine、Host、Context和Wrapper,這幾種子類型Container依次包含,處理不一樣粒度的請求。另外Container裏包含一些基礎服務,如Loader、Manager和Realm。
六、Engine:Engine包含Host和Context,接到請求後仍給相應的Host在相應的Context裏處理。
七、Host:就是咱們所理解的虛擬主機。
八、Context:就是咱們所部屬的具體Web應用的上下文,每一個請求都在是相應的上下文裏處理的
九、Wrapper:Wrapper是針對每一個Servlet的Container,每一個Servlet都有相應的Wrapper來管理。
能夠看出Server、Service、Connector、Container、Engine、Host、Context和Wrapper這些核心組件的做用範圍是逐層遞減,並逐層包含。
下面就是些被Container所用的基礎組件:
一、Loader:是被Container用來載入各類所需的Class。
二、Manager:是被Container用來管理Session池。
三、Realm:是用來處理安全裏受權與認證。
分析完核心類後,再看看Tomcat啓動的過程,Tomcat啓動的時序圖以下所示:
從上圖能夠看出,Tomcat啓動分爲init和start兩個過程,核心組件都實現了Lifecycle接口,都需實現start方法,所以在start過程當中就是從Server開始逐層調用子組件的start過程。
2)基於JMX
Tomcat會爲每一個組件進行註冊過程,經過Registry管理起來,而Registry是基於JMX來實現的,所以在看組件的init和start過程實際上就是初始化MBean和觸發MBean的start方法,會大量看到形如:
Registry.getRegistry(null, null).invoke(mbeans, "init", false); Registry.getRegistry(null, null).invoke(mbeans, "start", false); |
這樣的代碼,這實際上就是經過JMX管理各類組件的行爲和生命期。
3)事件偵聽
各個組件在其生命期中會有各類各樣行爲,而這些行爲都有觸發相應的事件,Tomcat就是經過偵聽這些時間達到對這些行爲進行擴展的目的。在看組件的init和start過程當中會看到大量如:
lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null); |
這樣的代碼,這就是對某一類型事件的觸發,若是你想在其中加入本身的行爲,就只用註冊相應類型的事件便可。
2、一次完整請求的裏裏外外
前幾天分析了一下Tomcat的架構和啓動過程,今天開始研究它的運起色制。Tomcat最本質就是個能運行JSP/Servlet的Web服務器 ,所以最典型的應用就是用戶經過瀏覽器訪問服務器,Tomcat接收到請求後轉發給Servlet,由Servlet處理完後,把結果返回給客戶端。今天就專門解析一下這麼一個完整的請求的內部機理。
經過DEBUG,一路跟下來,發現Tomcat處理請求的核心過程是如下幾點:
一、啓動的時候啓動預支持協議的Endpoint,Endpoint會起專門的線程監聽相應協議的請求,默認的狀況下,會啓動JIoEndpoint,JIoEndpoint基於Java ServerSocket接收Http的請求
二、ServerSocket接收到客戶端請求的Socket後,一路包裝,並一路從Host一直傳遞到Wrapper,再請求到相應的Servlet
下面將重點解析以上兩個過程。
經過之前的分析(Tomcat源碼分析一)可知道當Tomcat啓動的時候會啓動Connector,此時Connector會經過ProtocolHandler把Endpoint啓動起來。默認狀況下,Tomcat會啓動兩種Connector,分別是Http協議和AJP協議的,依次對應Http11Protocol和AjpProtocol,二者都是啓動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(); } } } |
以上代碼很清晰地表示啓動acceptorThreadCount個線程,每一個線程由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 } } |
由此可獲得這麼一個結論:Tomcat就是經過ServerSocket監聽Socket的方式來接收客戶端請求的。具體代碼就無需我解析了,稍微瞭解Java net的人都能看懂以上代碼,Tomcat就是用最標準和最基礎的Socket調用方法來處理網絡請求的。找處處理請求的源頭後下面要作的是事情就簡單了,打好斷點,在瀏覽器裏請求一個最簡單的Hello world,一路debug下去。一路跟下來,主流程的時序圖以下所示:
從上圖可知,以上過程可分解成如下三個最主要的核心點:
一、基於Http1.1協議對Socket的解析和包裝
二、StandardEngineValve、StandardHostValve、StandardContextValve和StandardWrapperValve四種Valve的一路inoke。四種不一樣層次的Valve作了不一樣層次的處理和封裝
三、基於責任鏈模式ApplicationFilterChain實現Filter攔截和實際Servlet的請求
以上三個核心點都是內容很是豐富的可研究點,會在之後幾天逐一進行剖析。
3、可攜帶狀態的線程池
最近想實現一個可攜帶狀態的線程池,具體需求就是池中的線程被用來處理某種信息,而此信息可視爲線程所依賴的外部狀態。若是用簡單的線程池來實現,線程初始化時就得賦予某些信息,使得線程沒法被再次利用。在看老版Tomcat的源碼時,找到了答案,其實現思路主要是利用了線程的等待和喚起,HttpProcessor的實現正好基於此思路,時序圖以下所示:
初始化HttpProcessor線程時,無法賦予所需的Socket對象,由於若是在初始化階段就賦予Socket會致使此線程無法回收用來處理其餘Socket。所以,在HttpProcessor的run階段,先把線程給wait住,具體在await方法裏體現,代碼以下所示:
/** * Await a newly assigned Socket from our Connector, or * 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); } null |
/** * Await a newly assigned Socket from our Connector, or * 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); } null |
當HttpConnector調用HttpProcessor.assign(socket)方法時,會給此線程賦予Socket對象,並喚起此線程,使其繼續執行,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對象後,繼續執行核心的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(); } } |
4、Request和Response處理的全過程
從Tomcat源碼分析(二)可知,用戶的一個請求會通過n個環節的處理,最後到達開發人員寫的Servlet,傳給Servlet也就是HttpServletRequest和HttpServletResponse,所以能夠認爲這一路走下來無非就是把最原始的Socket包裝成Servlet裏用到的HttpServletRequest和HttpServletResponse,只不過每一個環節完成的包裝功能和部分不同而已,信息流以下圖所示:
其中,Request與Response的類圖以下所示:
org.apache.coyote.Request和org.apache.coyote.Response是Tomcat內部使用的,不提供給開發者調用,類是final類型的。下面結合一次完整請求的時序圖來看看從Socket到org.apache.catalina.connector.Request的加工過程:
由上圖可見,Request的解析和加工過程不是在一個方法裏搞定,而是信息流動過程當中逐步解析的,不一樣層次的處理器解析不一樣層次的信息,在解析過程同時作了些判斷和攔截的工做,好比當發現是要訪問WEB-INF的資源,會直接返回錯誤給客戶端等等。
來自:
http://www.uml.org.cn/j2ee/201306285.asp