一、Tomcat
Tomcat,全名Apache Tomcat,最初是由Sun發起,後來捐贈給ASF,是Apache Jakarta下的一個子專案。
Tomcat是對Servlet API定義的容器的一個完整實現,同時它也不僅僅是一個容器,也完全可以當做一個純Java實現的HTTP伺服器來使用,Tomcat自帶的Servlet容器的名稱為Catalina。
Tomcat 的心臟是兩個元件:Connector 和 Container,一個 Container 可以選擇對應多個 Connector。多個 Connector(多個Connector的原因應該是多種網路協議吧) 和一個 Container 就形成了一個 Service,有了 Service 就可以對外提供服務了,但是 Service 還要一個生存的環境,該環境由Server提供,所以整個 Tomcat 的生命週期由 Server 控制。
即:
Server:控制Tomcat的start/stop,通過server的start/stop,能夠一路暢通地把下面所有的service/connector/container一起start/stop
Service:配置多個Connector的目的一般是為了適應不同的http協議,即不同的協議由不同的connector處理,但都關聯到一個Container
Container:Container由四個級別,有高層到底層分別為Engine、Host、Context、Wrapper,具體介紹在下一篇部落格解釋
二、Tomcat聯結器Connector的啟動
Tomcat不推薦使用預設聯結器,而是使用Coyote(郊狼),但預設的連結器依然是一個比較好的學習範本。本小結的分析就是基於預設連機器。
Connector 元件的主要任務是負責接收瀏覽器的發過來的 tcp 連線請求,建立一個 Request 和 Response 物件分別用於和請求端交換資料,然後會產生一個執行緒來處理這個請求,並把 Request 和 Response 物件傳給處理這個請求的執行緒。
Connector聯結器處理請求的時序圖:
下面是Connector啟動的入口:
public final class Bootstrap { public static void main(String[] args) { HttpConnector connector = new HttpConnector(); SimpleContainer container = new SimpleContainer(); connector.setContainer(container); connector.initialize();//對應上圖第一步,建立全域性的ServerSocket connector.start(); } }
HttpConnector的start方法如下:
public void start() throws LifecycleException { lifecycle.fireLifecycleEvent(START_EVENT, null); started = true; // Start our background thread threadStart(); // Create the specified minimum number of processors while (curProcessors < minProcessors) { if ((maxProcessors > 0) && (curProcessors >= maxProcessors)) break; //建立多個processor,並且每個processor都呼叫了本執行緒的start方法進行啟動 //將process壓入堆疊,該堆疊用來儲存多個processor HttpProcessor processor = newProcessor(); recycle(processor); } }
threadStart()會啟動後臺執行緒,於是進入該connector的run()方法,上圖中的第3步
private void threadStart() { thread = new Thread(this, threadName); thread.setDaemon(true); thread.start(); }
public void run() { while (!stopped) { // Accept the next incoming connection from the server socket Socket socket = null; try { socket = serverSocket.accept();//connector會阻塞在accept方法,等待http連線 socket.setTcpNoDelay(tcpNoDelay); } // Hand this socket off to an appropriate processor // 連線帶來後獲取processor,獲取方式很簡單,就是從stack中彈出一個 HttpProcessor processor = createProcessor(); // 呼叫processor的assign方法,assing會通知processor執行緒去處理具體的動作 // 本處呼叫直接返回,提高了效率 processor.assign(socket); // The processor will recycle itself when it finishes }//結束while synchronized (threadSync) { threadSync.notifyAll(); }
}//結束run
上述為Connector執行緒的啟動過程,下面就要講Processor執行緒的處理過程了,注意二者是不同的處理執行緒。
上面講到processor執行緒進行了啟動,即每個執行緒都執行了run方法,該run方法如下:
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 process(socket); // Finish up this request connector.recycle(this); } // Tell threadStop() we have shut ourselves down successfully synchronized (threadSync) { threadSync.notifyAll(); } }
await方法會阻塞住,直到被通知,這是上圖中的第5步驟,這個通知的發出是由connector呼叫processor的assign方法發出的。await()和assing的實現如下:
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); } Procesor的assign方法如下: 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(); }
整個過程描述總結如下:
1、processor執行緒啟動的時候,avaliable=false,執行緒會在await方法中等待,直到其他執行緒呼叫notify()或者notifyAll()方法。
2、客戶端發起請求,新的socket通過assing()方法傳入processor,此時avaliable依然為false,assign方法會跳過while,設定available=true,併發出通知,告訴processor執行緒可以向下繼續執行
3、processor執行緒收到通知,此時avaliable=true,await方法會跳過while,執行process方法,進行處理。
4、process方法中,將處理好request和response, connector.getContainer().invoke(request, response);交給container去處理。問題是,到底交個container中的哪個servlet去處理呢?後續文章介紹吧
參考資料
《Tomcat深度剖析》
https://www.ibm.com/developerworks/cn/java/j-lo-tomcat1/