How tomcat works 第四章學習筆記(1)

Tomcat4默認連接器

tomcat連接器是一個獨立的模塊,可以插入到一個servlet容器。一個tomcat連接器必須符合以下要求:

 

  • 必須實現org.apache.catalina.Connector接口
  • 必須創建一個實現org.apache.catalina.Request接口的request對象
  • 必須創建一個實現org.apache.catalina.Response接口的response對象

Container接口的invoke方法:

 

    /**
     * Process the specified Request, and generate the corresponding Response,
     * according to the design of this particular Container.
     *
     * @param request Request to be processed
     * @param response Response to be produced
     *
     * @exception IOException if an input/output error occurred while
     *  processing
     * @exception ServletException if a ServletException was thrown
     *  while processing this request
     */
    public void invoke(Request request, Response response)
        throws IOException, ServletException;

 在invoke方法中,容器加載servlet類、調用service方法、管理session、記錄錯誤信息日誌等等。

 

使用對象池來降低複雜對象的創建開銷。

 

*HTTP1.1新特性

 

  • 持久連接

在HTTP1.0中,每對Request/Response都使用一個新的連接。

HTTP 1.1則支持Persistent Connection, 並且默認使用persistent connection.

connection: keep-alive

  • 塊編碼

HTTP1.1支持chunked transfer,所以可以有Transfer-Encoding頭部域,HTTP1.0則沒有。

Transfer-Encoding: chunked

 

 

1D\r\n 
I'm as helpless as a kitten u 
9\r\n
p a tree. 
0\r\n
  • 狀態100的使用

100 (Continue) 狀態代碼的使用,允許客戶端在發request消息body之前先用request header試探一下server,看server要不要接收request body,再決定要不要發request body。

客戶端在Request頭部中包含Expect: 100-continue

Server看到之後呢如果回100 (Continue) 這個狀態代碼,客戶端就繼續發request body。

HTTP/1.1 100 Continue

 

HttpConnector類

(1)如何創建一個server socket?

(2)如何維護HttpProcessor池?

(3)如何處理Http請求?

org.apache.catalina.connetor.http.HttpConnector類,實現了org.apache.catalina.Connector、java.lang.Runnable和org.apache.catalina.LifeCycle接口。LifeCycle接口用來維護每一個實現了此接口的Catalina組件的生命週期。

 

  • 創建一個server socket

 

Java代碼
  1. private ServerSocket open() throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException,  
  2.     UnrecoverableKeyException, KeyManagementException {  
  3.   
  4.     // Acquire the server socket factory for this Connector  
  5.     ServerSocketFactory factory = getFactory();  
  6.   
  7.     // If no address is specified, open a connection on all addresses  
  8.     if (address == null) {  
  9.         log(sm.getString("httpConnector.allAddresses"));  
  10.         try {  
  11.             return (factory.createSocket(port, acceptCount));  
  12.         } catch (BindException be) {  
  13.             throw new BindException(be.getMessage() + ":" + port);  
  14.         }  
  15.     }  
  16.   
  17.     // Open a server socket on the specified address  
  18.     try {  
  19.         InetAddress is = InetAddress.getByName(address);  
  20.         log(sm.getString("httpConnector.anAddress", address));  
  21.         try {  
  22.             return (factory.createSocket(port, acceptCount, is));  
  23.         } catch (BindException be) {  
  24.             throw new BindException(be.getMessage() + ":" + address + ":" + port);  
  25.         }  
  26.     } catch (Exception e) {  
  27.         log(sm.getString("httpConnector.noAddress", address));  
  28.         try {  
  29.             return (factory.createSocket(port, acceptCount));  
  30.         } catch (BindException be) {  
  31.             throw new BindException(be.getMessage() + ":" + port);  
  32.         }  
  33.     }  
  34.   
  35. }  
  • 如何維護HttpProcessor池

首先採用棧來存儲HttpProcessor實例,HttpProcessor池動態擴容,根據三個屬性來設置:

 

curProcessors: 當前HttpProcessor實例的個數

minProcessors : int 初始化時,最小的HttpProcessor實例個數

maxProcessors:最大HttpProcessor實例個數,當小於0時,不做限制

 

初始化時創建最小HttpProcessor實例個數代碼:

 

 

Java代碼
  1. // Create the specified minimum number of processors  
  2.         while (curProcessors < minProcessors) {  
  3.             if ((maxProcessors > 0) && (curProcessors >= maxProcessors))  
  4.                 break;  
  5.             HttpProcessor processor = newProcessor();  
  6.             recycle(processor);  
  7.         }  
 

 

 

  • 處理http請求

HttpConnector類在自己的run方法中有自己的主要邏輯,直到HttpConnetor停止之前都會一直等待接收http請求。對於每一個http請求,通過調用createProcessor方法獲得一個HttpProcessor實例。然而,大多數時間,createProcessor方法並不會創建一個新的HttpProcessor對象,而是從一個HttpProcessor池中獲取。如果在這個池中(實際採用的是堆棧來存儲)有一個HttpProcessor實例可供使用,執行出棧操作。如果棧爲空且仍然沒有超過HttpProcessor實例的最大個數,則創建一個新的HttpProcessor實例,否則,createProcessor方法將返回null,此時socket執行關閉操作而不會響應到來的http請求。如果createProcessor方法沒有返回null,客戶端的socket傳遞給HttpProcessor的assign方法。

 

Java代碼
  1. private HttpProcessor createProcessor() {  
  2.   
  3.        synchronized (processors) {  
  4.            if (processors.size() > 0) {  
  5.                return ((HttpProcessor) processors.pop());  
  6.            }  
  7.            if ((maxProcessors > 0) && (curProcessors < maxProcessors)) {  
  8.                return (newProcessor());  
  9.            } else {  
  10.                if (maxProcessors < 0) {  
  11.                    return (newProcessor());  
  12.                } else {  
  13.                    return (null);  
  14.                }  
  15.            }  
  16.        }  
  17.   
  18.    }  

  HttpProcessor類

 

在本章,我們最感興趣的是HttpProcessor類如何使assign方法異步以便HttpConnetor實例可以同時爲多個http請求服務。HttpProcessor類中另外一個重要的方法是私有方法process方法,它解析了http請求並且調用了容器的invoke方法。

第三章中,HttpConnetor在自己的線程中運行,然而它必須等待當前處理的http請求結束之後纔可以處理下一個請求。

第三章的HttpConnetor類的run方法代碼如下:

 

    public void run() {
        ServerSocket serverSocket = null;
        int port = 8080;
        try {
            serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
        while (!stopped) {
            // Accept the next incoming connection from the server socket
            Socket socket = null;
            try {
                socket = serverSocket.accept();
            } catch (Exception e) {
                continue;
            }
            // Hand this socket off to an HttpProcessor
            HttpProcessor processor = new HttpProcessor(this);
            processor.process(socket);
        }
    }

 HttpProcessor類的process方法在第三章中是同步方法。因此,它的run方法等待直到process方法處理結束才接收下一個請求。在本章,默認的連接器的HttpProcessor類實現了Runnable接口,因此每一個HttpProcessor實例都運行在自己的線程中,我們稱爲“processor線程”

 

Lifecycle接口的start和stop方法

 

 

    /**
     * Prepare for the beginning of active use of the public methods of this
     * component.  This method should be called before any of the public
     * methods of this component are utilized.  It should also send a
     * LifecycleEvent of type START_EVENT to any registered listeners.
     *
     * @exception LifecycleException if this component detects a fatal error
     *  that prevents this component from being used
     */
    public void start() throws LifecycleException;


    /**
     * Gracefully terminate the active use of the public methods of this
     * component.  This method should be the last one called on a given
     * instance of this component.  It should also send a LifecycleEvent
     * of type STOP_EVENT to any registered listeners.
     *
     * @exception LifecycleException if this component detects a fatal error
     *  that needs to be reported
     */
    public void stop() throws LifecycleException;

 

 

HttpProcessor實現了Lifecycle接口,因此HttpProcessor類的start方法代碼如下:

 

    /**
     * 判斷HttpProcessor組件是否啓動
     */
    private boolean started = false;
/**
     * Start the background thread we will use for request processing.
     *
     * @exception LifecycleException if a fatal startup error occurs
     */
    public void start() throws LifecycleException {

        if (started)
            throw new LifecycleException
                (sm.getString("httpProcessor.alreadyStarted"));
        lifecycle.fireLifecycleEvent(START_EVENT, null);
        started = true;

        threadStart();

    }
 

 

 

    /**
     * 開啓後臺處理線程
     */
    private void threadStart() {

        log(sm.getString("httpProcessor.starting"));

        thread = new Thread(this, threadName);
        thread.setDaemon(true);
        thread.start();

        if (debug >= 1)
            log(" Background thread has been started");

    }

   HttpProcessor的run方法的while循環執行流程:獲得一個socket,處理它,調用connector的recycle(回收)方法把當前的HttpProcessor實例壓回棧中。注意到while循環中停止到await方法處,await方法掌握着“processor thread”的控制流,直到它從HttpConnetor獲得到一個新的socket對象。換句話說,直到HttpConnetor類調用HttpProcessor實例的assign方法。

 

            HttpProcessor processor = createProcessor();
            processor.assign(socket);

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

    }

將調用完的HttpProcessor實例壓回棧中的代碼實現:

 

  void recycle(HttpProcessor processor) {
        processors.push(processor);

    }

然而,await方法和assign方法運行在不同的線程中,assign方法是在HttpConnetor的run方法中被調用的,即“connector線程”。

那麼assign方法是如何告訴await方法它被調用了呢?

利用一個布爾型變量available和java.lang.Object類的wait和notifyAll方法。

 

注:Object的wait方法導致當前線程等待直到其他線程對這個對象調用notify或者nitifyAll方法。

 

connetor線程調用的assign方法:

 

 

    /**
     * Process an incoming TCP/IP connection on the specified socket.  Any
     * exception that occurs during processing must be logged and swallowed.
     * <b>NOTE</b>:  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");

    }

 HttpProcessor線程調用的await方法:

 

    /**
     * Await a newly assigned Socket from our Connector, or <code>null</code>
     * 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);

    }

 初始時,當“processor thread”剛啓動時,available爲false,即還沒有可用的socket。因此線程在while循環中等待,直到其他線程調用notify或者notifyAll方法。也就是說,調用wait方法導致“processor thread”暫停直到“connector thread”對這個HttpProcessor實例調用notifyAll方法。當一個新的socket被分配,“connector thread”調用HttpProcessor的assign方法。avilable置爲true,喚醒“processor thread”。

 

爲什麼await方法需要使用一個本地變量(socket)且不返回這個socket變量的實例?

 

因爲HttpProcessor實例的socket變量在當前socket處理完之前還可以分配給下一個到來的socket。

 

爲什麼await方法需要調用notifyAll?

爲了當另外一個socket到達的時候此時available爲true,這時候,“connector thread”將會停止裏面的assign方法直到收到“processor thread”的notifyAll方法。

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