聲明:源碼版本爲Tomcat 6.0.35java
前面的文章中介紹了Tomcat的基本配置,每一個配置項也基本上對應了Tomcat的組件結構,若是要用一張圖來形象展示一下Tomcat組成的話,整個Tomcat的組成能夠以下圖所示:web
Tomcat在接收到用戶請求時,將會經過以上組件的協做來給最終用戶產生響應。首先是最外層的Server和Service來提供整個運行環境的基礎設施,而Connector經過指定的協議和接口來監聽用戶的請求,在對請求進行必要的處理和解析後將請求的內容傳遞給對應的容器,通過容器一層層的處理後,生成最終的響應信息,返回給客戶端。apache
Tomcat的容器經過實現一系列的接口,來統一處理一些生命週期相關的操做,而Engine、Host、Context等容器經過實現Container接口來完成處理請求時統一的模式,具體表現爲該類容器內部均有一個Pipeline結構,實際的業務處理都是經過在Pipeline上添加Valve來實現,這樣就充分保證整個架構的高度可擴展性。Tomcat核心組件的類圖以下圖所示:網絡
在介紹請求的處理過程時,將會詳細介紹各個組件的做用和處理流程。本文將會主要分析Tomcat的啓動流程,介紹涉及到什麼組件以及初始化的過程,簡單期間將會重點分析HTTP協議所對應Connector啓動過程。架構
Tomcat在啓動時的重點功能以下:socket
初始化類加載器:主要初始化CommonLoader、CatalinaLoader以及SharedLoader;this
解析配置文件:使用Digester組件解析Tomcat的server.xml,初始化各個組件(包含各個web應用,解析對應的web.xml進行初始化);spa
初始化鏈接器:初始化聲明的Connector,以指定的協議打開端口,等待請求。命令行
無論是經過命令行啓動仍是經過Eclipse的WST server UI,Tomcat的啓動流程是在org.apache.catalina.startup. Bootstrap類的main方法中開始的,在啓動時,這個類的核心代碼以下所示:線程
public static void main(String args[]) { if (daemon == null) { daemon = new Bootstrap();//實例化該類的一個實例 try { daemon.init();//進行初始化 } catch (Throwable t) { ……; } } try { ……//此處略去代碼若干行 if (command.equals("start")) { daemon.setAwait(true); daemon.load(args);//執行load,生成組件實例並初始化 daemon.start();//啓動各個組件 } ……//此處略去代碼若干行 }
從以上的代碼中,能夠看到在Tomcat啓動的時候,執行了三個關鍵方法即init、load、和start。後面的兩個方法都是經過反射調用org.apache.catalina.startup.Catalina的同名方法完成的,因此後面在介紹時將會直接轉到Catalina的同名方法。首先分析一下Bootstrap的init方法,在該方法中將會初始化一些全局的系統屬性、初始化類加載器、經過反射獲得Catalina實例,在這裏咱們重點看一下初始化類加載器的方法:
private void initClassLoaders() { try { commonLoader = createClassLoader("common", null); if( commonLoader == null ) { // no config file, default to this loader - we might be in a 'single' env. commonLoader=this.getClass().getClassLoader(); } catalinaLoader = createClassLoader("server", commonLoader); sharedLoader = createClassLoader("shared", commonLoader); } catch (Throwable t) { log.error("Class loader creation threw exception", t); System.exit(1); } }
在以上的代碼總,咱們能夠看到初始化了三個類加載器,這三個類加載器將會有篇博文進行簡單的介紹。
而後咱們進入Catalina的load方法:
public void load() { //…… //初始化Digester組件,定義瞭解析規則 Digester digester = createStartDigester(); //……中間略去代碼若干,主要做用爲將server.xml文件轉換爲輸入流 try { inputSource.setByteStream(inputStream); digester.push(this); //經過Digester解析這個文件,在此過程當中會初始化各個組件實例及其依賴關係 digester.parse(inputSource); inputStream.close(); } catch (Exception e) { } // 調用Server的initialize方法,初始化各個組件 if (getServer() instanceof Lifecycle) { try { getServer().initialize(); } catch (LifecycleException e) { if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) throw new java.lang.Error(e); else log.error("Catalina.start", e); } } }
在以上的代碼中,關鍵的任務有兩項即便用Digester組件按照給定的規則解析server.xml、調用Server的initialize方法。關於Digester組件的使用,後續會有一篇專門的博文進行講解,而Server的initialize方法中,會發布事件並調用各個Service的initialize方法,從而級聯完成各個組件的初始化。每一個組件的初始化都是比較有意思的,可是咱們限於篇幅先關注Connector的初始化,這多是最值得關注的。
Connector的initialize方法,核心代碼以下:
public void initialize() throws LifecycleException{ //該適配器會完成請求的真正處理 adapter = new CoyoteAdapter(this); //對於不一樣的實現,會有不一樣的ProtocolHandler實現類,咱們來看 //Http11Protocol,它用來處理HTTP請求 protocolHandler.setAdapter(adapter); try { protocolHandler.init(); } catch (Exception e) { …… } }
在Http11Protocol的init方法中,核心代碼以下:
public void init() throws Exception { endpoint.setName(getName());//endpoint爲JIoEndpoint的實現類 endpoint.setHandler(cHandler); try { endpoint.init();//核心代碼就是調用 JIoEndpoint的初始化方法 } catch (Exception ex) { …… } }
咱們看到最終的初始化方法最終都會調到JIoEndpoint的init方法,網絡初始化和對請求的最初處理都是經過該類及其內部類完成的,因此後續的內容將會重點關注此類:
public void init() throws Exception { if (acceptorThreadCount == 0) {//接受請求的線程數 acceptorThreadCount = 1; } if (serverSocket == null) { try { if (address == null) { //基於特定端口建立一個ServerSocket對象,準備接受請求 serverSocket = serverSocketFactory.createSocket(port, backlog); } else { serverSocket = serverSocketFactory.createSocket(port, backlog, address); } } catch (BindException orig) { …… } } }
在上面的代碼中,咱們能夠看到此時初始化了一個ServerSocket對象,用來準備接受請求。
若是將其比做賽跑,此時已經到了「各就各位」狀態,就等最終的那聲「發令槍」了,而Catalina的start方法就是「發令槍」啦:
public void start() { if (getServer() == null) { load(); } if (getServer() == null) { log.fatal("Cannot start server. Server instance is not configured."); return; } if (getServer() instanceof Lifecycle) { try { ((Lifecycle) getServer()).start(); } catch (LifecycleException e) { log.error("Catalina.start: ", e); } } //…… }
此時會調用Server的start方法,這裏咱們重點仍是關注JIoEndpoint的start方法:
public void start() throws Exception { if (!initialized) { init(); } if (!running) { running = true; paused = false; if (executor == null) { //初始化處理鏈接的線程,maxThread的默認值爲200,這也就是爲何 //說Tomcat只能同時處理200個請求的來歷 workers = new WorkerStack(maxThreads); } for (int i = 0; i < acceptorThreadCount; i++) { //初始化接受請求的線程 Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i); acceptorThread.setPriority(threadPriority); acceptorThread.setDaemon(daemon); acceptorThread.start(); } } }
從以上的代碼,能夠看到,若是沒有在server.xml中聲明Executor的話,將會使用內部的一個容量爲200的線程池用來後續的請求處理。而且按照參數acceptorThreadCount的設置,初始化線程來接受請求。而Acceptor是真正的幕後英雄,接受請求並分派給處理過程:
protected class Acceptor implements Runnable { public void run() { while (running) { // 接受發送過來的請求 Socket socket = serverSocketFactory.acceptSocket(serverSocket); serverSocketFactory.initSocket(socket); //處理這個請求 if (!processSocket(socket)) { //關閉鏈接 try { socket.close(); } catch (IOException e) { // Ignore } } } } }
從這裏咱們能夠看到,Acceptor接受Socket請求,並調用processSocket方法來進行請求的處理。至此,Tomcat的組件整裝待命,等待請求的到來。關於請求的處理,會在下篇文章中介紹。