Tomcat源碼分析--轉


一、架構

下面談談我對Tomcat架構的理解

總體架構:

1、面向組件架構

2、基於JMX

3、事件偵聽

1)面向組件架構

tomcat代碼看似很龐大,但從結構上看卻很清晰和簡單,它主要由一堆組件組成,如Server、Service、Connector等,並基於JMX管理這些組件,另外實現以上接口的組件也實現了代表生存期的接口Lifecycle,使其組件履行固定的生存期,在其整個生存期的過程中通過事件偵聽LifecycleEvent實現擴展。Tomcat的核心類圖如下所示:

1、Catalina:與開始/關閉shell腳本交互的主類,因此如果要研究啟動和關閉的過程,就從這個類開始看起。

2、Server:是整個Tomcat組件的容器,包含一個或多個Service。

3、Service:Service是包含Connector和Container的集合,Service用適當的Connector接收用戶的請求,再發給相應的Container來處理。

4、Connector:實現某一協議的連接器,如默認的有實現HTTP、HTTPS、AJP協議的。

5、Container:可以理解為處理某類型請求的容器,處理的方式一般為把處理請求的處理器包裝為Valve對象,並按一定順序放入類型為Pipeline的管道里。Container有多種子類型:Engine、Host、Context和Wrapper,這幾種子類型Container依次包含,處理不同粒度的請求。另外Container里包含一些基礎服務,如Loader、Manager和Realm。

6、Engine:Engine包含Host和Context,接到請求后仍給相應的Host在相應的Context里處理。

7、Host:就是我們所理解的虛擬主機。

8、Context:就是我們所部屬的具體Web應用的上下文,每個請求都在是相應的上下文里處理的

9、Wrapper:Wrapper是針對每個Servlet的Container,每個Servlet都有相應的Wrapper來管理。

可以看出Server、Service、Connector、Container、Engine、Host、Context和Wrapper這些核心組件的作用范圍是逐層遞減,並逐層包含。

下面就是些被Container所用的基礎組件:

1、Loader:是被Container用來載入各種所需的Class。

2、Manager:是被Container用來管理Session池。

3、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);

這樣的代碼,這就是對某一類型事件的觸發,如果你想在其中加入自己的行為,就只用注冊相應類型的事件即可。

二、一次完整請求的里里外外

前幾天分析了一下Tomcat的架構和啟動過程,今天開始研究它的運轉機制。Tomcat最本質就是個能運行JSP/Servlet的Web服務器 ,因此最典型的應用就是用戶通過瀏覽器訪問服務器,Tomcat接收到請求后轉發給Servlet,由Servlet處理完后,把結果返回給客戶端。今天就專門解析一下這么一個完整的請求的內部機理。

通過DEBUG,一路跟下來,發現Tomcat處理請求的核心過程是以下幾點:

1、啟動的時候啟動預支持協議的Endpoint,Endpoint會起專門的線程監聽相應協議的請求,默認的情況下,會啟動JIoEndpoint,JIoEndpoint基於Java ServerSocket接收Http的請求

2、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下去。一路跟下來,主流程的時序圖如下所示:

從上圖可知,以上過程可分解成以下三個最主要的核心點:

1、基於Http1.1協議對Socket的解析和包裝

2、StandardEngineValve、StandardHostValve、StandardContextValve和StandardWrapperValve四種Valve的一路inoke。四種不同層次的Valve做了不同層次的處理和封裝

3、基於責任鏈模式ApplicationFilterChain實現Filter攔截和實際Servlet的請求

以上三個核心點都是內容非常豐富的可研究點,會在以后幾天逐一進行剖析。

三、可攜帶狀態的線程池

最近想實現一個可攜帶狀態的線程池,具體需求就是池中的線程被用來處理某種信息,而此信息可視為線程所依賴的外部狀態。如果用簡單的線程池來實現,線程初始化時就得賦予某些信息,使得線程無法被再次利用。在看老版Tomcat的源碼時,找到了答案,其實現思路主要是利用了線程的等待和喚起,HttpProcessor的實現正好基於此思路,時序圖如下所示:

初始化HttpProcessor線程時,沒法賦予所需的Socket對象,因為如果在初始化階段就賦予Socket會導致此線程沒法回收用來處理其他Socket。因此,在HttpProcessor的run階段,先把線程給wait住,具體在await方法里體現,代碼如下所示:

/** 
 * 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);  
  
}  

當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();  
    }  
  
}  

四、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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM