Tomcat源碼分析(二)–連接處理 – JAVA編程語言程序開發技術文章

 目標:在這篇文章希望搞明白http請求到tomcat後是怎麼由連接器轉交到容器的?
      在上一節裡已經啟動瞭一個HttpConnector線程,並且也啟動瞭固定數量的HttpProcessor線程。HttpConnector用來等待http連接,得到http連接後交給其中的一個HttpProcessor線程來處理。接下裡具體看一下HttpConnector是怎麼得到連接得,以及HttpProcessor是怎麼處理的。當啟動瞭HttpConnector線程後(在上一節已經知道怎麼啟動瞭),便在它的run方法裡面循環等待:
[java] 
public void run() { 
    // Loop until we receive a shutdown command 
    while (!stopped) { 
        // Accept the next incoming connection from the server socket 
        Socket socket = null; 
        try { 
            socket = serverSocket.accept(); 
            if (connectionTimeout > 0) 
                socket.setSoTimeout(connectionTimeout); 
            socket.setTcpNoDelay(tcpNoDelay); 
        } catch (AccessControlException ace) { 
            log("socket accept security exception", ace); 
            continue; 
        } catch (IOException e) { 
            try { 
                // If reopening fails, exit 
                synchronized (threadSync) { 
                    if (started && !stopped) 
                        log("accept error: ", e); 
                    if (!stopped) { 
                        serverSocket.close(); 
                        serverSocket = open(); 
                    } 
                } 
                 
            } catch (IOException ioe) { 
                log("socket reopen, io problem: ", ioe); 
                break; 
            } catch (KeyStoreException kse) { 
                log("socket reopen, keystore problem: ", kse); 
                break; 
            } catch (NoSuchAlgorithmException nsae) { 
                log("socket reopen, keystore algorithm problem: ", nsae); 
                break; 
            } catch (CertificateException ce) { 
                log("socket reopen, certificate problem: ", ce); 
                break; 
            } catch (UnrecoverableKeyException uke) { 
                log("socket reopen, unrecoverable key: ", uke); 
                break; 
            } catch (KeyManagementException kme) { 
                log("socket reopen, key management problem: ", kme); 
                break; 
            } 
 
            continue; 
        } 
 
        // Hand this socket off to an appropriate processor 
        HttpProcessor processor = createProcessor(); 
        if (processor == null) { 
            try { 
                log(sm.getString("httpConnector.noProcessor")); 
                socket.close(); 
            } catch (IOException e) { 
                ; 
            } 
            continue; 
        } 
      
        processor.assign(socket); 
 
    } 
 
    // Notify the threadStop() method that we have shut ourselves down 
  
    synchronized (threadSync) { 
        threadSync.notifyAll(); 
    } 
 

這裡很關鍵的就是socket = serverSocket.accept();和processor.assign(socket); 在循環裡面內,serverSocket.accept();負責接收http請求然後賦值給socket,最後交給其中一個processor處理。這裡processor並不是等到需要的時候再實例化,而是在HttpConnector初始化的時候已經有瞭若幹個processor,在httpConnector裡有這樣一個聲明:
[java] 
private Stack processors = new Stack(); 
表明httpConnector裡面持有一個包含HttpProcessor對象的棧,需要的時候拿出來就是瞭。看一下createProcessor函數就能比較明白瞭:
[java] 
private HttpProcessor createProcessor() { 
     synchronized (processors) { 
         if (processors.size() > 0) { 
             return ((HttpProcessor) processors.pop()); //從processors棧中彈出一個processor 
         } 
         if ((maxProcessors > 0) && (curProcessors < maxProcessors)) { 
             return (newProcessor()); 
         } else { 
             if (maxProcessors < 0) { 
                 return (newProcessor()); 
             } else { 
                 return (null); 
             } 
         } 
     } 
 
 } 

接下來由processor.assign(socket); 記住這個方法是異步的,不需要等待HttpProcessor來處理完成,所以HttpConnector才能不間斷的傳入Http請求,在HttpProcessor裡有兩個方法比較重要,這兩個方法協調處理瞭由HttpConnector傳來的socket:
[java] 
 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"); 
 

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

看一下HttpProcessor的run方法:
[java] 
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(); 
       } 
 
   } 

很明顯,在它的run方法一開始便是調用上面的await方法來等待(因為一開始available變量為false),所以HttpProcessor會一直阻塞,直到有線程來喚醒它。當從HttpConnector中調用processor.assign(socket),會把socket傳給此HttpProcessor對象,並設置available為true,調用notifyAll()喚醒該processor線程以處理socket。同時,在await方法中又把available設置成false,因此又回到初始狀態,即可以重新接受socket。
這裡處理socket的方法是process(socket),主要作用有兩點,1:解析這個socket,即解析http請求,包括請求方法,請求協議等,以填充request,response對象(是不是很熟悉,在servlet和jsp開發經常用到的request,response對象就是從這裡來的)。2:傳入request,response對象給和HttpConnector綁定的容器,讓容器來調用invoke方法進行處理。process方法主要的代碼如下:www.aiwalls.com
[java] 
private void process(Socket socket) { 
                   input = new SocketInputStream(socket.getInputStream(), 
                                          connector.getBufferSize()); 
                   //解析一下連接的地址,端口什麼的 
                    parseConnection(socket); 
                    //解析請求頭的第一行,即:方法,協議,uri 
                    parseRequest(input, output); 
                    if (!request.getRequest().getProtocol() 
                        .startsWith("HTTP/0")) 
                    parseHeaders(input);//解析http協議的頭部 
                    ………………………………………. 
                    connector.getContainer().invoke(request, response); 
                    ……………………………………… 

作者:haitao111313

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *