How Tomcat Works學習筆記

 

Tomcat4默認連接器

Tomcat的連接器(Connector)採用模塊化思想設計,作爲一個獨立的模塊與Servlet Container相連接。目前已經有多種連接器,像Coyote、mod_jk、mod_jk2、mod_webapp等,Tomcat4中的默認連接器目前已經被速度更快的Coyote代替。但是Tomcat4的默認連接器仍然是一個很好的學習工具,有助於理解Tomcat連接器的原理,所有的Tomcat連接器只需滿足一下三大:

1、  實現org.apache.catalina.Connector接口;

2、  創建實現了org.apache.catalina.Request接口的request對象;

3、  創建實現了org.apache.catalina.Response接口的response對象。

 

默認連接器會調用偵聽HTTP請求,創建request和response通過調用org.apache.catalina.Container接口的invoke方法把request和response傳給Container:

public void invoke(org.apache.catalina.Request request, org.apache.catalina.Response response);

同時默認連接器提供了一個池用來緩存變量,並把很多String改成了char數組。

默認連接器全面支持HTTP1.1,同時兼任HTTP0.9和HTTP1.0.

 

HTTP1.1的新特性

1.       支持持久化TCP連接。在以前版本中一旦服務端收到請求相關資源,馬上就被關閉,但是一個請求頁面中可能會引用很多其它信息,如圖片等,如果每個資源都用一個連接會比較耗時,完全可以在一個連接裏面處理。HTTP1.1中增加了支持化連接,解決了上述問題。通過request header中指定connection:keep-alive實現

2.       支持塊編碼,分塊傳輸。

3.       傳輸一個繼續狀態(100)。

Connector接口

         所有的Tomcat連接器都必須實現org.apache.catalina.Connector接口,其中提供了很多需要實現的方法,最重要的有getContainer、setContainer、createRequest和createResponse方法。

         setContainer用來爲連接器分配一個servlet容器,getContainer獲取分配給連接器的容器,createRequest爲來自客戶端的請求創建一個request對象,createResponse爲請求創建響應(Response)。Connector的類圖如下:

         其中Connector與Container的關係式一對一的,而HttpConnector與HttpProcessor對象是一對多的。

HttpConnector類

         HttpConnector是Connector的實現類,即Tomcat默認的連接器類,作爲組件(Component)必須實現org.apache.catalina.Lifecycle,具有生命週期,要在Tomcat中創建一個連接器必須先調用它的init方法進行初始化,然後調用start方法啓動。

創建ServerSocket

         在調用HttpConnector的啓動方法的時候,會創建一個ServerSocket對象,用來偵聽客戶端的請求。在創建ServerSocket對象的時候使用了工廠模式,提供了一個工廠接口ServerSocketFactory與工廠實現類DefaultServerSocketFactory,我先在這裏應該是爲了簡化ServerSocket創建的過程,便於處理多個端口的情況。

緩存HttpProcessor實例

         爲了併發處理Request,提高服務器的處理效率,這裏用Stack提供了對HttpProcessor進行緩衝,同時通過minProcessors和maxProcessors來控制併發數,一旦請求超過maxProcessors將會被忽略。

爲HTTP Request服務

         對於偵聽到的每一個HTTP請求(Socket),HttpConnector都會去從緩衝棧中獲取一個處理器HttpProcessor,並把獲取到的Socket分配剛剛獲取到的HttpProcessor。

HttpProcessor對象

         Http 請求處理器是一個線程,不斷調用await方法獲取socket,一旦取到以後就調用process方法進行處理,處理完成以後會把當前處理器放入緩衝棧裏面。

         run方法循環調用await獲取socket進行處理,處理完成以後把處理器放回緩衝棧:

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

        }

    }

         await方法獲取socket,如果不能獲取調用wait方法把當前線程掛起:

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

    }

assign方法爲處理器分配需要處理的socket,並喚醒處理器中的線程處理socket:

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");

    }

Request對象

         在默認連接器中Http請求用org.apache.catalina.Request接口表示具體類關係見下圖:

Response對象

         Response接口及其相關類如下圖:

 

處理請求

         一旦HttpProcessor組件中線程獲取到socket以後,會調用process方法對request請求進行處理,主要做下面三步:

1、  解析connection

2、  解析request

3、  解析request header

簡單servlet容器

         要測試連接器還需要提供兩個類Bootstray和SimpleContainer,Bootstray用來啓動應用,SimpleContainer是servlet容器,實現org.ahache.catalina.Container接口,其中有一個關鍵方法inveke回去加載servlet並調用其的service方法:

         public void invoke(Request request, Response response)

    throws IOException, ServletException {

    String servletName = ( (HttpServletRequest) request).getRequestURI();

    servletName = servletName.substring(servletName.lastIndexOf("/") + 1);

    URLClassLoader loader = null;

    try {

      URL[] urls = new URL[1];

      URLStreamHandler streamHandler = null;

      File classPath = new File(WEB_ROOT);

      String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;

      urls[0] = new URL(null, repository, streamHandler);

      loader = new URLClassLoader(urls);

    }

    catch (IOException e) {

      System.out.println(e.toString() );

    }

    Class myClass = null;

    try {

      myClass = loader.loadClass(servletName);

    }

    catch (ClassNotFoundException e) {

      System.out.println(e.toString());

    }

    Servlet servlet = null;

    try {

      servlet = (Servlet) myClass.newInstance();

      servlet.service((HttpServletRequest) request, (HttpServletResponse) response);

    }

    catch (Exception e) {

      System.out.println(e.toString());

    }

    catch (Throwable e) {

      System.out.println(e.toString());

    }

  }

Bootstray會構造連接器和容器,把容器分配給連接器,先調用連接器的init方法進行初始化,然後調用連接器的start方法啓動連接器,如下:

public final class Bootstrap {

  public static void main(String[] args) {

    HttpConnector connector = new HttpConnector();

    SimpleContainer container = new SimpleContainer();

    connector.setContainer(container);

    try {

      connector.initialize();

      connector.start();

      // make the application wait until we press any key.

      System.in.read();

    }

    catch (Exception e) {

      e.printStackTrace();

    }

  }

}

運行應用

要運行應用,需要導入servlet-api.jar(servlet2.4以後)或servlet.jar(servlet2.3),同時在需要把PrimitiveServlet.java編譯後放在webroot下,在頁面瀏覽器中輸入

        http://localhost:8080/servlet/PrimitiveServlet

將會在頁面輸出:

         Hello.Roses are red. Violets are blue.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章