一、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/
