tomcat架構


很多開源應用服務器都是集成tomcat作為web container的,而且對於tomcat的servlet container這部分代碼很少改動。這樣,這些應用服務器的性能基本上就取決於Tomcat處理HTTP請求的connector模塊的性能。本文首先從應用層次分析了tomcat所有的connector種類及用法,接着從架構上分析了connector模塊在整個tomcat中所處的位置,最后對connector做了詳細的源代碼分析。並且我們以Http11NioProtocol為例詳細說明了tomcat是如何通過實現ProtocolHandler接口而構建connector的。

 一、Connector介紹

1. Connector

在Tomcat架構中,Connector主要負責處理與客戶端的通信。Connector的實例用於監聽端口,接受來自客戶端的請求並將請求轉交給Engine處理。同時將來自Engine的答復返回給客戶端。

2. Connector的種類

Tomcat源碼中與connector相關的類位於org.apache.coyote包中,Connector分為以下幾類:

Http Connector, 基於HTTP協議,負責建立HTTP連接。它又分為BIO Http Connector與NIO Http Connector兩種,后者提供非阻塞IO與長連接Comet支持。默認情況下,Tomcat使用的就是這個Connector。

AJP Connector, 基於AJP協議,AJP是專門設計用來為tomcat與http服務器之間通信專門定制的協議,能提供較高的通信速度和效率。如與Apache服務器集成時,采用這個協議。

APR HTTP Connector, 用C實現,通過JNI調用的。主要提升對靜態資源(如HTML、圖片、CSS、JS等)的訪問性能。現在這個庫已獨立出來可用在任何項目中。Tomcat在配置APR之后性能非常強勁。

具體地,Tomcat7中實現了以下幾種Connector:

org.apache.coyote.http11.Http11Protocol : 支持HTTP/1.1 協議的連接器。

org.apache.coyote.http11.Http11NioProtocol : 支持HTTP/1.1 協議+New IO的連接器。

org.apache.coyote.http11.Http11AprProtocol : 使用APR(Apache portable runtime)技術的連接器,利用Native代碼與本地服務器(如linux)來提高性能。

(以上三種Connector實現都是直接處理來自客戶端Http請求,加上NIO或者APR)

org.apache.coyote.ajp.AjpProtocol:使用AJP協議的連接器,實現與web server(如Apache httpd)之間的通信

org.apache.coyote.ajp.AjpNioProtocol:SJP協議+ New IO

org.apache.coyote.ajp.AjpAprProtocol:AJP + APR

(這三種實現方法則是與web server打交道,同樣加上NIO和APR)

當然,我們可以通過實現ProtocolHandler接口來定義自己的Connector。詳細的實現過程請看文章后文。

3. Connector的配置

對Connector的配置位於conf/server.xml文件中,內嵌在Service元素中,可以有多個Connector元素。

(1) BIO HTTP/1.1 Connector配置

一個典型的配置如下:  

<Connector  port=”8080”  protocol=”HTTP/1.1”  maxThreads=”150”  conn ectionTimeout=”20000”   redirectPort=”8443” />
其它一些重要屬性如下:
acceptCount : 接受連接request的最大連接數目,默認值是10
address : 綁定IP地址,如果不綁定,默認將綁定任何IP地址
allowTrace : 如果是true,將允許TRACE HTTP方法
compressibleMimeTypes : 各個mimeType, 以逗號分隔,如text/html,text/xml
compression : 如果帶寬有限的話,可以用GZIP壓縮
connectionTimeout : 超時時間,默認為60000ms (60s)
maxKeepAliveRequest : 默認值是100
maxThreads : 處理請求的Connector的線程數目,默認值為200

如果是SSL配置,如下:

<Connector port="8181" protocol="HTTP/1.1" SSLEnabled="true" 
    maxThreads="150" scheme="https" secure="true" 
    clientAuth="false" sslProtocol = "TLS" 
    address="0.0.0.0" 
    keystoreFile="E:/java/jonas-full-5.1.0-RC3/conf/keystore.jks" 
    keystorePass="changeit" />

其中,keystoreFile為證書位置,keystorePass為證書密碼

(2) NIO HTTP/1.1 Connector配置

<Connector port=”8080” protocol=”org.apache.coyote.http11.Http11NioProtocol” maxThreads=”150” connectionTimeout=”20000” redirectPort=”8443”/>

(3) Native APR Connector配置

  • ARP是用C/C++寫的,對靜態資源(HTML,圖片等)進行了優化。所以要下載本地庫tcnative-1.dll與openssl.exe,將其放在%tomcat%\bin目錄下。

下載地址是:http://tomcat.heanet.ie/native/1.1.10/binaries/win32/

  • 在server.xml中要配置一個Listener,如下圖。這個配置tomcat是默認配好的。
    <!--APR library loader. Documentation at /docs/apr.html --> 
    <Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  • 配置使用APR connector
    <Connector port=”8080” protocol=”org.apache.coyote.http11.Http11AprProtocol” 
    maxThreads=”150” connectionTimeout=”20000” redirectPort=”8443”
  •  
如果配置成功,啟動tomcat,會看到如下信息:
org.apache.coyote.http11.Http11AprProtocol init
(4)  AJP Connector配置:
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

二、Connector在Tomcat中所處的位置

1. Tomcat架構

>>>Server(服務器)是Tomcat構成的頂級構成元素,所有一切均包含在Serv    erz中,Server的實現類StandardServer可以包含一個到多個Services;>>>>Service的實現類為StandardService調用了容器(Container)     接口,其實是調用了Servlet Engine(引擎),而且StandardService類中也   指明了該Service歸屬的Server;

  • >>>Container,引擎(Engine)、主機(Host)  上下文(Context)和Wraper均繼承自Container接口,所以它們都是容器       但是,它們是有父子關系的,在主機(Host)、上下文(Context)和引擎(En      gine)這三類容器中,引擎是頂級容器,直接包含是主機容器,而主機容       器又包含上下文容器,所以引擎、主機和上下文從大小上來說又構成父子     關系,雖然它們都繼承自Container接口。
  • >>>連接器(Connector)將Service和Container連接起來,首先它需要注冊到一個Service,它的作用就是把來自客戶端的請求轉發到Container(容器),這就是它為什么稱作連接器的原因。

故我們從功能的角度將Tomcat源代碼分成5個子模塊,它們分別是:

Jsper子模塊:這個子模塊負責jsp頁面的解析、jsp屬性的驗證,同時也負責將jsp頁面動態轉換為java代碼並編譯成class文件。在Tomcat源代碼中,凡是屬於org.apache.jasper包及其子包中的源代碼都屬於這個子模塊;

Servlet和Jsp規范的實現模塊:這個子模塊的源代碼屬於javax.servlet包及其子包,如我們非常熟悉的javax.servlet.Servlet接口、javax.servet.http.HttpServlet類及javax.servlet.jsp.HttpJspPage就位於這個子模塊中;

Catalina子模塊:這個子模塊包含了所有以org.apache.catalina開頭的java源代碼。該子模塊的任務是規范了Tomcat的總體架構,定義了Server、Service、Host、Connector、Context、Session及Cluster等關鍵組件及這些組件的實現,這個子模塊大量運用了Composite設計模式。同時也規范了Catalina的啟動及停止等事件的執行流程。從代碼閱讀的角度看,這個子模塊應該是我們閱讀和學習的重點。

Connector子模塊:如果說上面三個子模塊實現了Tomcat應用服務器的話,那么這個子模塊就是Web服務器的實現。所謂連接器(Connector)就是一個連接客戶和應用服務器的橋梁,它接收用戶的請求,並把用戶請求包裝成標准的Http請求(包含協議名稱,請求頭Head,請求方法是Get還是Post等等)。同時,這個子模塊還按照標准的Http協議,負責給客戶端發送響應頁面,比如在請求頁面未發現時,connector就會給客戶端瀏覽器發送標准的Http 404錯誤響應頁面。

Resource子模塊:這個子模塊包含一些資源文件,如Server.xml及Web.xml配置文件。嚴格說來,這個子模塊不包含java源代碼,但是它還是Tomcat編譯運行所必需的。

2.Tomcat運行流程

假設來自客戶的請求為:http://localhost:8080/test/index.jsp.請求被發送到本機端口8080,被在那里偵聽的Coyote HTTP/1.1 Connector獲得,然后

 

  • Connector把該請求交給它所在的Service的Engine來處理,並等待Engine的回應
  • Engine獲得請求localhost:8080/test/index.jsp,匹配它所有虛擬主機Host
  • Engine匹配到名為localhost的Host(即使匹配不到也把請求交給該Host處理,因為該Host被定義為該Engine的默認主機)
  • localhost Host獲得請求/test/index.jsp,匹配它所擁有的所有Context
  • Host匹配到路徑為/test的Context(如果匹配不到就把該請求交給路徑名為""的Context去處理)
  • path="/test"的Context獲得請求/index.jsp,在它的mapping table中尋找對應的servlet
  • Context匹配到URL PATTERN為*.jsp的servlet,對應於JspServlet類,構造HttpServletRequest對象和HttpServletResponse對象,作為參數調用JspServlet的doGet或doPost方法
  • Context把執行完了之后的HttpServletResponse對象返回給Host
  • Host把HttpServletResponse對象返回給Engine
  • Engine把HttpServletResponse對象返回給Connector
  • Connector把HttpServletResponse對象返回給客戶browser

三、Connector源碼分析

1 Tomcat的啟動分析與集成設想

我們知道,啟動tomcat有兩種方式:雙擊bin/startup.bat、運行bin/catalina.bat run。它們對應於Bootstrap與Catalina兩個類,我們現在只關心Catalina這個類,這個類使用Apache Digester解析conf/server.xml文件生成tomcat組件,然后再調用Embedded類的start方法啟動tomcat。

所以,集成Tomcat的方式就有以下兩種了:

①沿用tomcat自身的server.xml

②自己定義一個xml格式來配置tocmat的各參數,自己再寫解析這段xml,然后使用tomcat提供的API根據這些xml來生成Tomcat組件,最后調用Embedded類的start方法啟動tomcat

個人覺得第一種方式要優越,給開發者比較好的用戶體驗,如果使用這種,直接模仿Catalina類的方法即可實現集成。

目前,JOnAS就使用了這種集成方式,JBoss、GlassFish使用的第二種自定義XML的方式。

2. Connector類圖與順序圖

  

 

  

從上面二圖中我們可以得到如下信息:

Tomcat中有四種容器(Context、Engine、Host、Wrapper),前三者常見,第四個不常見但它也是實現了Container接口的容器

如果要自定義一個Connector的話,只需要實現org.apache.coyote.ProtocolHander接口,該接口定義如下:

 

[java]  view plain  copy
  1. <span style="font-size:14px;">/* 
  2.  *  Licensed to the Apache Software Foundation (ASF) under one or more 
  3.  *  contributor license agreements.  See the NOTICE file distributed with 
  4.  *  this work for additional information regarding copyright ownership. 
  5.  *  The ASF licenses this file to You under the Apache License, Version 2.0 
  6.  *  (the "License"); you may not use this file except in compliance with 
  7.  *  the License.  You may obtain a copy of the License at 
  8.  * 
  9.  *      http://www.apache.org/licenses/LICENSE-2.0 
  10.  * 
  11.  *  Unless required by applicable law or agreed to in writing, software 
  12.  *  distributed under the License is distributed on an "AS IS" BASIS, 
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  14.  *  See the License for the specific language governing permissions and 
  15.  *  limitations under the License. 
  16.  */  
  17.   
  18. package org.apache.coyote;  
  19.   
  20. import java.util.concurrent.Executor;  
  21.   
  22.   
  23. /** 
  24.  * Abstract the protocol implementation, including threading, etc. 
  25.  * Processor is single threaded and specific to stream-based protocols, 
  26.  * will not fit Jk protocols like JNI. 
  27.  * 
  28.  * This is the main interface to be implemented by a coyote connector. 
  29.  * Adapter is the main interface to be implemented by a coyote servlet 
  30.  * container. 
  31.  * 
  32.  * @author Remy Maucherat 
  33.  * @author Costin Manolache 
  34.  * @see Adapter 
  35.  */  
  36. public interface ProtocolHandler {   //Tomcat 中的Connector實現了這個接口  
  37.   
  38.     /** 
  39.      * The adapter, used to call the connector. 
  40.      */  
  41.     public void setAdapter(Adapter adapter);  
  42.     public Adapter getAdapter();  
  43.   
  44.   
  45.     /** 
  46.      * The executor, provide access to the underlying thread pool. 
  47.      */  
  48.     public Executor getExecutor();  
  49.   
  50.   
  51.     /** 
  52.      * Initialise the protocol. 
  53.      */  
  54.     public void init() throws Exception;  
  55.   
  56.   
  57.     /** 
  58.      * Start the protocol. 
  59.      */  
  60.     public void start() throws Exception;  
  61.   
  62.   
  63.     /** 
  64.      * Pause the protocol (optional). 
  65.      */  
  66.     public void pause() throws Exception;  
  67.   
  68.   
  69.     /** 
  70.      * Resume the protocol (optional). 
  71.      */  
  72.     public void resume() throws Exception;  
  73.   
  74.   
  75.     /** 
  76.      * Stop the protocol. 
  77.      */  
  78.     public void stop() throws Exception;  
  79.   
  80.   
  81.     /** 
  82.      * Destroy the protocol (optional). 
  83.      */  
  84.     public void destroy() throws Exception;  
  85.   
  86.   
  87.     /** 
  88.      * Requires APR/native library 
  89.      */  
  90.     public boolean isAprRequired();  
  91. }  
  92. </span>  

自定義Connector時需實現的ProtoclHandler接口

 

Tomcat以HTTP(包括BIO與NIO)、AJP、APR、內存四種協議實現了該接口(它們分別是:AjpAprProtocol、AjpProtocol、Http11AprProtocol、Http11NioProtocol、Http11Protocal、JkCoyoteHandler、MemoryProtocolHandler),要使用哪種Connector就在conf/server.xml中配置,在Connector的構造函數中會通過反射實例化所配置的實現類:

<Connector port="8181" 
   protocol="org.apache.coyote.http11.Http11AprProtocol " />
 
3.Connector的工作流程
下面我們以org.apache.coyote.http11.Http11AprProtocol為例說明Connector的工作流程。
①它將工作委托給AprEndpoint類。
[java]  view plain  copy
  1. <span style="font-size:14px;">    public Http11AprProtocol() {  
  2.         endpoint = new AprEndpoint();                    // 主要工作由AprEndpoint來完成  
  3.         cHandler = new Http11ConnectionHandler(this);    // inner class  
  4.         ((AprEndpoint) endpoint).setHandler(cHandler);</span>  

②在AprEndpoint.Acceptor類中的run()方法會接收一個客戶端新的連接請求.

 

[java]  view plain  copy
  1. /** 
  2.  * The background thread that listens for incoming TCP/IP connections and 
  3.  * hands them off to an appropriate processor. 
  4.  */  
  5. protected class Acceptor extends AbstractEndpoint.Acceptor {  
  6.   
  7.     private final Log log = LogFactory.getLog(AprEndpoint.Acceptor.class);  
  8.   
  9.     @Override  
  10.     public void run() {  
  11.   
  12.         int errorDelay = 0;  
  13.   
  14.         // Loop until we receive a shutdown command  
  15.         while (running) {  


 

③在AprEndpoint類中,有一個內部接口Handler,該接口定義如下:

 

[java]  view plain  copy
  1. <span style="font-size:14px;">/** 
  2.      * Bare bones interface used for socket processing. Per thread data is to be 
  3.      * stored in the ThreadWithAttributes extra folders, or alternately in 
  4.      * thread local fields. 
  5.      */  
  6.     public interface Handler extends AbstractEndpoint.Handler {  
  7.         public SocketState process(SocketWrapper<Long> socket,  
  8.                 SocketStatus status);  
  9.     }</span>  


 

④在Http11AprProtocol類中實現了AprEndpoint中的Handler接口,

 

[java]  view plain  copy
  1. protected static class Http11ConnectionHandler  
  2.            extends AbstractConnectionHandler<Long,Http11AprProcessor> implements Handler {  
  3.          

並調用Http11AprProcessor類(該類實現了ActionHook回調接口)。

 

       @Override

 

[java]  view plain  copy
  1. <span style="font-size:14px;">        protected Http11AprProcessor createProcessor() {  
  2.             Http11AprProcessor processor = new Http11AprProcessor(</span>  

 

 

四、 通過Connector實現一個簡單的Server。
通過以下這些步驟,你就可以建立一個很簡單的服務器,除了用到tomcat的一些類之外,跟Tomcat沒有任何關系。
首先,建立一個含有main()方法的java類,詳細代碼如下:
[java]  view plain  copy
  1. package myConnector;  
  2.   
  3. import java.util.concurrent.BlockingQueue;  
  4. import java.util.concurrent.LinkedBlockingQueue;  
  5. import java.util.concurrent.ThreadPoolExecutor;  
  6. import java.util.concurrent.TimeUnit;  
  7.   
  8. import org.apache.coyote.http11.Http11Protocol;  
  9.   
  10.   
  11. /** 
  12.  * 基於Http11Protocol實現一個簡單的服務器 
  13.  * @author bingduanLin 
  14.  * 
  15.  */  
  16. public class MyServer {  
  17.   
  18.     /** 
  19.      * @param args 
  20.      */  
  21.     public static void main(String[] args) throws Exception{  
  22.         Http11Protocol hp = new Http11Protocol();  
  23.         hp.setPort(9000);  
  24.         ThreadPoolExecutor threadPoolExecutor = createThreadPoolExecutor();  
  25.         threadPoolExecutor.prestartCoreThread();  
  26.         hp.setExecutor(threadPoolExecutor);  
  27.         hp.setAdapter(new myHandler());  
  28.         hp.init();  
  29.         hp.start();  
  30.         System.out.println("My Server has started successfully!");  
  31.           
  32.   
  33.     }  
  34.       
  35.     public static ThreadPoolExecutor createThreadPoolExecutor() {  
  36.         int corePoolSite = 2 ;  
  37.         int maxPoolSite = 10 ;  
  38.         long keepAliveTime = 60 ;  
  39.         TimeUnit unit = TimeUnit.SECONDS;  
  40.         BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();  
  41.         ThreadPoolExecutor threadPoolExecutor =   
  42.                 new ThreadPoolExecutor(corePoolSite, maxPoolSite,  
  43.                         keepAliveTime, unit, workQueue);  
  44.         return threadPoolExecutor;  
  45.     }  
  46.   
  47. }  

上面這個類用到的MyHandler類代碼如下:
[java]  view plain  copy
  1. package myConnector;  
  2.   
  3. import java.io.ByteArrayOutputStream;  
  4. import java.io.OutputStreamWriter;  
  5. import java.io.PrintWriter;  
  6.   
  7. import org.apache.coyote.Adapter;  
  8. import org.apache.coyote.Request;  
  9. import org.apache.coyote.Response;  
  10. import org.apache.tomcat.util.buf.ByteChunk;  
  11. import org.apache.tomcat.util.net.SocketStatus;  
  12.   
  13. public class myHandler implements Adapter {  
  14.   
  15.     @Override  
  16.     public void service(Request req, Response res) throws Exception {  
  17.         // 請求處理  
  18.         System.out.println("Hi, Boss. I am handling the reuqest!");  
  19.         ByteArrayOutputStream baos = new ByteArrayOutputStream();     
  20.         PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos));     
  21.         writer.println("Not Hello World");     
  22.         writer.flush();     
  23.     
  24.         ByteChunk byteChunk = new ByteChunk();     
  25.         byteChunk.append(baos.toByteArray(), 0, baos.size());     
  26.         res.doWrite(byteChunk);    
  27.   
  28.     }  
  29.   
  30.     @Override  
  31.     public boolean event(Request req, Response res, SocketStatus status)  
  32.             throws Exception {  
  33.         System.out.println("Event-Event");  
  34.         return false;  
  35.     }  
  36.   
  37.     @Override  
  38.     public boolean asyncDispatch(Request req, Response res, SocketStatus status)  
  39.             throws Exception {  
  40.         // TODO Auto-generated method stub  
  41.         return false;  
  42.     }  
  43.   
  44.     @Override  
  45.     public void log(Request req, Response res, long time) {  
  46.         // TODO Auto-generated method stub  
  47.   
  48.     }  
  49.   
  50.     @Override  
  51.     public String getDomain() {  
  52.         // TODO Auto-generated method stub  
  53.         return null;  
  54.     }  
  55.   
  56. }  

注意,必須導入相關的tomcat源碼文件。完成之后運行主程序即:MyServer。然后在瀏覽器中輸入:localhost:9000就可以看到“Not Hello World”。
下面是主程序的一些輸出:
十二月 20, 2012 8:46:12 下午 org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-bio-9000"]
十二月 20, 2012 8:46:13 下午 org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-bio-9000"]
My Server has started successfully!
Hi, Boss. I am handling the reuqest!
Hi, Boss. I am handling the reuqest!
Hi, Boss. I am handling the reuqest!
Hi, Boss. I am handling the reuqest!
 
是不是很酷??
 
備注: (本文以ChinaUnix.net的原文為基礎做修改,原文請參考:http://tech.chinaunix.net/a2011/1214/1288/000001288290_2.shtml)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM