Tomcat7.0源码分析——请求原理分析

Tomcat7.0源码分析——请求原理分析

  谈起Tomcat的诞生,最早可以追溯到1995年。近20年来,Tomcat始终是使用最广泛的Web服务器,由于其使用Java语言开发,所以广为Java程序员所熟悉。很多人早期的J2EE项目,由程序员自己实现Jsp页面或者Servlet接受请求,后来借助Struts1、Struts2、spring等中间件后,实际也是利用Filter或者Servlet处理请求,大家肯定要问了,这些Servlet处理的请求来自哪里?Tomcat作为Web服务器是怎样将HTTP请求交给Servlet的呢?

  本文就Tomcat对HTTP的请求处理细节进行分析。

  提示:阅读本文前,请确保首先理解了《Tomcat7.0源码分析——生命周期管理》中的内容。

Connector的初始化

  根据《Tomcat7.0源码分析——生命周期管理》一文的内容,我们知道Tomcat中有很多容器,包括Server、Service、Connector等。其中Connector正是与HTTP请求处理相关的容器。Service是Server的子容器,而Connector又是Service的子容器。那么这三个容器的初始化顺序为:Server->Service->Connector。Connector的实现分为以下几种:

  • Http Connector:基于HTTP协议,负责建立HTTP连接。它又分为BIO Http Connector与NIO Http Connector两种,后者提供非阻塞IO与长连接Comet支持。
  • AJP Connector:基于AJP协议,AJP是专门设计用于Tomcat与HTTP服务器通信定制的协议,能提供较高的通信速度和效率。如与Apache服务器集成时,采用这个协议。
  • APR HTTP Connector:用C实现,通过JNI调用的。主要提升对静态资源(如HTML、图片、CSS、JS等)的访问性能。现在这个库已独立出来可用在任何项目中。由于APR性能较前两类有很大提升,所以目前是Tomcat的默认Connector。现在我们直接来看Connector的initInternal方法吧,见代码清单1。
代码清单1

[java]  view plain  copy
 
  1. @Override  
  2. protected void initInternal() throws LifecycleException {  
  3.   
  4.     super.initInternal();  
  5.       
  6.     // Initialize adapter  
  7.     adapter = new CoyoteAdapter(this);  
  8.     protocolHandler.setAdapter(adapter);  
  9.   
  10.     IntrospectionUtils.setProperty(protocolHandler, "jkHome",  
  11.                                    System.getProperty("catalina.base"));  
  12.   
  13.     onameProtocolHandler = register(protocolHandler,  
  14.             createObjectNameKeyProperties("ProtocolHandler"));  
  15.       
  16.     mapperListener.setDomain(getDomain());  
  17.   
  18.     onameMapper = register(mapperListener,  
  19.             createObjectNameKeyProperties("Mapper"));  
  20. }  

代码清单1说明了Connector的初始化步骤如下:

步骤一 构造网络协议处理的CoyoteAdapter

  代码清单1构造了CoyoteAdapter对象,并且将其设置为ProtocolHandler的Adapter。ProtocolHandler是做什么的呢?Tomcat处理HTTP请求,需要有一个ServerSocket监听网络端口来完成任务。接口ProtocolHandler被设计成控制网络端口监听组件运行,负责组件的生命周期控制,这个接口实际并没有定义网络端口监听功能的规范,而是用于负责维护组件的生命周期。从ProtocolHandler的名字来看,它应该是网络协议的处理者,但它实际不负责这个功能,而是将其交给org.apache.coyote.Adapter来完成,这么设计估计是为了方便维护和拓展新功能。Http11Protocol是ProtocolHandler接口的一个实现(是Connector的默认处理协议),被设计用来处理HTTP1.1网络协议的请求,通过该类可以完成在某个网络端口上面的监听,同时以HTTP1.1的协议来解析请求内容,然后将请求传递到Connector所寄居的Container容器pipeline流水工作线上处理。此处的ProtocolHandler是何时生成的呢?还记得《Tomcat7.0源码分析——SERVER.XML文件的加载与解析》一文中的Digester和Rule吗?Digester在解析到标签的时候,会执行startElement方法,startElement中会调用Rule的begin(String namespace, String name, Attributes attributes)方法,Connector对应的Rule包括ConnectorCreateRule,ConnectorCreateRule的begin方法的实现见代码清单2。

代码清单2

[java]  view plain  copy
 
  1. @Override  
  2. public void begin(String namespace, String name, Attributes attributes)  
  3.         throws Exception {  
  4.     Service svc = (Service)digester.peek();  
  5.     Executor ex = null;  
  6.     if ( attributes.getValue("executor")!=null ) {  
  7.         ex = svc.getExecutor(attributes.getValue("executor"));  
  8.     }  
  9.     Connector con = new Connector(attributes.getValue("protocol"));  
  10.     if ( ex != null )  _setExecutor(con,ex);  
  11.       
  12.     digester.push(con);  
  13. }  
代码清单2中调用了Connector的构造器,传递的参数为属性protocol。我们知道server.xml中的Connector有两个:

[html]  view plain  copy
 
  1. <Connector port="8080" protocol="HTTP/1.1"   
  2.            connectionTimeout="20000"   
  3.            redirectPort="8443" />  
  4.   
  5. <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />  

我们看看Connector的构造器实现,见代码清单3。

代码清单3

[java]  view plain  copy
 
  1. public Connector(String protocol) {  
  2.     setProtocol(protocol);  
  3.     // Instantiate protocol handler  
  4.     try {  
  5.         Class clazz = Class.forName(protocolHandlerClassName);  
  6.         this.protocolHandler = (ProtocolHandler) clazz.newInstance();  
  7.     } catch (Exception e) {  
  8.         log.error  
  9.             (sm.getString  
  10.              ("coyoteConnector.protocolHandlerInstantiationFailed", e));  
  11.     }  
  12. }  

setProtocol方法(见代码清单4)根据protocol参数的不同,调用setProtocolHandlerClassName方法(见代码清单5)设置protocolHandlerClassName属性。以HTTP/1.1为例,由于默认情况下Apr不可用,所以protocolHandlerClassName会被设置为org.apache.coyote.http11.Http11Protocol,那么反射生成的protocolHandler就是Http11Protocol实例。Tomcat默认还会配置协议是AJP/1.3的Connector,那么此Connector的protocolHandler就是org.apache.coyote.ajp.AjpProtocol。 

代码清单4

[java]  view plain  copy
 
  1. /** 
  2.  * Set the Coyote protocol which will be used by the connector. 
  3.  * 
  4.  * @param protocol The Coyote protocol name 
  5.  */  
  6. public void setProtocol(String protocol) {  
  7.   
  8.     if (AprLifecycleListener.isAprAvailable()) {  
  9.         if ("HTTP/1.1".equals(protocol)) {  
  10.             setProtocolHandlerClassName  
  11.                 ("org.apache.coyote.http11.Http11AprProtocol");  
  12.         } else if ("AJP/1.3".equals(protocol)) {  
  13.             setProtocolHandlerClassName  
  14.                 ("org.apache.coyote.ajp.AjpAprProtocol");  
  15.         } else if (protocol != null) {  
  16.             setProtocolHandlerClassName(protocol);  
  17.         } else {  
  18.             setProtocolHandlerClassName  
  19.                 ("org.apache.coyote.http11.Http11AprProtocol");  
  20.         }  
  21.     } else {  
  22.         if ("HTTP/1.1".equals(protocol)) {  
  23.             setProtocolHandlerClassName  
  24.                 ("org.apache.coyote.http11.Http11Protocol");  
  25.         } else if ("AJP/1.3".equals(protocol)) {  
  26.             setProtocolHandlerClassName  
  27.                 ("org.apache.coyote.ajp.AjpProtocol");  
  28.         } else if (protocol != null) {  
  29.             setProtocolHandlerClassName(protocol);  
  30.         }  
  31.     }  
  32.   
  33. }  

代码清单5

[java]  view plain  copy
 
  1. public void setProtocolHandlerClassName(String protocolHandlerClassName) {  
  2.   
  3.     this.protocolHandlerClassName = protocolHandlerClassName;  
  4.   
  5. }  

除此之外,ProtocolHandler还有其它实现,如图1所示。


图1  ProtocolHandler类继承体系

图1中有关ProtocolHandler的实现类都在org.apache.coyote包中 。前面所说的BIO Http Connector实际就是Http11Protocol,NIO Http Connector实际就是Http11NioProtocol,AJP Connector包括AjpProtocol和AjpAprProtocol,APR HTTP Connector包括AjpAprProtocol、Http11AprProtocol,此外还有一个MemoryProtocolHandler(这个是做什么的,目前没搞清楚,有知道的同学告诉我下啊!)。

步骤二 将ProtocolHandler、MapperListener注册到JMX

  BIO Http Connector的ProtocolHandler(即Http11Protocol)的JMX注册名为Catalina:type=ProtocolHandler,port=8080。BIO Http Connector的MapperListener的注册名为Catalina:type=Mapper,port=8080。AJP Connector的ProtocolHandler(即AjpProtocol)的JMX注册名为Catalina:type=ProtocolHandler,port=8009。AJP Connector的MapperListener的注册名为Catalina:type=Mapper,port=8009。有关Tomcat中JMX注册的内容,请阅读《Tomcat7.0源码分析——生命周期管理》一文。

Connector的启动

  根据《Tomcat7.0源码分析——生命周期管理》一文的内容,我们知道Tomcat中有很多容器。ProtocolHandler的初始化稍微有些特殊,Server、Service、Connector这三个容器的初始化顺序为:Server->Service->Connector。值得注意的是,ProtocolHandler作为Connector的子容器,其初始化过程并不是由Connector的initInternal方法调用的,而是与启动过程一道被Connector的startInternal方法所调用。由于本文的目的是分析请求,所以直接从Connector的startInternal方法(见代码清单6)开始。

代码清单6

[java]  view plain  copy
 
  1. /** 
  2.  * Begin processing requests via this Connector. 
  3.  * 
  4.  * @exception LifecycleException if a fatal startup error occurs 
  5.  */  
  6. @Override  
  7. protected void startInternal() throws LifecycleException {  
  8.   
  9.     setState(LifecycleState.STARTING);  
  10.   
  11.     // Protocol handlers do not follow Lifecycle conventions.  
  12.     // protocolHandler.init() needs to wait until the connector.start()  
  13.     try {  
  14.         protocolHandler.init();  
  15.     } catch (Exception e) {  
  16.         throw new LifecycleException  
  17.             (sm.getString  
  18.              ("coyoteConnector.protocolHandlerInitializationFailed", e));  
  19.     }  
  20.   
  21.     try {  
  22.         protocolHandler.start();  
  23.     } catch (Exception e) {  
  24.         String errPrefix = "";  
  25.         if(this.service != null) {  
  26.             errPrefix += "service.getName(): \"" + this.service.getName() + "\"; ";  
  27.         }  
  28.   
  29.         throw new LifecycleException  
  30.             (errPrefix + " " + sm.getString  
  31.              ("coyoteConnector.protocolHandlerStartFailed", e));  
  32.     }  
  33.   
  34.     // MapperListener doesn't follow Lifecycle conventions either gja  
  35.     mapperListener.init();  
  36. }  

代码清单6说明了Connector的startInternal方法的执行顺序如下:

  1. 将Connector容器的状态更改为启动中(LifecycleState.STARTING);
  2. 初始化ProtocolHandler;
  3. 启动ProtocolHandler;
  4. 初始化MapperListener。

初始化ProtocolHandler

  简单起见,我们以Http11Protocol为例剖析ProtocolHandler的init方法,其实现见代码清单7。

代码清单7

[java]  view plain  copy
 
  1. @Override  
  2. public void init() throws Exception {  
  3.     ((JIoEndpoint)endpoint).setName(getName());  
  4.     ((JIoEndpoint)endpoint).setHandler(cHandler);  
  5.   
  6.     // Verify the validity of the configured socket factory  
  7.     try {  
  8.         if (isSSLEnabled()) {  
  9.             sslImplementation =  
  10.                 SSLImplementation.getInstance(sslImplementationName);  
  11.             socketFactory = sslImplementation.getServerSocketFactory();  
  12.             ((JIoEndpoint)endpoint).setServerSocketFactory(socketFactory);  
  13.         } else if (socketFactoryName != null) {  
  14.             socketFactory = (ServerSocketFactory) Class.forName(socketFactoryName).newInstance();  
  15.             ((JIoEndpoint)endpoint).setServerSocketFactory(socketFactory);  
  16.         }  
  17.     } catch (Exception ex) {  
  18.         log.error(sm.getString("http11protocol.socketfactory.initerror"),  
  19.                   ex);  
  20.         throw ex;  
  21.     }  
  22.   
  23.     if (socketFactory!=null) {  
  24.         Iterator attE = attributes.keySet().iterator();  
  25.         while( attE.hasNext() ) {  
  26.             String key = attE.next();  
  27.             Object v=attributes.get(key);  
  28.             socketFactory.setAttribute(key, v);  
  29.         }  
  30.     }  
  31.       
  32.     try {  
  33.         endpoint.init();  
  34.     } catch (Exception ex) {  
  35.         log.error(sm.getString("http11protocol.endpoint.initerror"), ex);  
  36.         throw ex;  
  37.     }  
  38.     if (log.isInfoEnabled())  
  39.         log.info(sm.getString("http11protocol.init", getName()));  
  40.   
  41. }  

从代码清单7看到,Http11Protocol的初始化步骤如下:

步骤一 设置JIoEndpoint的名称

  JIoEndpoint的名称默认为http-8080,这里的JIoEndpoint是在调用Http11Protocol的构造器时创建的,Http11Protocol的构造器中还设置了socket的延迟关闭选项soLingerOn、socket的延时关闭秒数soLingerTime、socket连接超时时间soTimeout、提高socket性能的tcpNoDelay等选项,见代码清单8。

代码清单8

[java]  view plain  copy
 
  1. public Http11Protocol() {  
  2.     endpoint = new JIoEndpoint();  
  3.     setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);  
  4.     setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);  
  5.     //setServerSoTimeout(Constants.DEFAULT_SERVER_SOCKET_TIMEOUT);  
  6.     setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);  
  7.       
  8. }  

步骤二 设置JIoEndpoint的Handler

  JIoEndpoint的handler被设置为cHandler,此cHandler的定义如下:

[java]  view plain  copy
 
  1. protected Http11ConnectionHandler cHandler = new Http11ConnectionHandler(this);  

步骤三 配置ServerSocketFactory

从代码清单7看到,生成ServerSocketFactory有三种方式:

  • 如果在server.xml中配置Connector时指定了SSLEnabled="true"的属性,那么创建带有SSL(Secure Sockets Layer 安全套接层)的ServerSocketFactory;
  • 如果Http11Protocol指定了socketFactoryName,则使用socketFactoryName反射生成ServerSocketFactory实例;
  • 如果不满足以上2个条件,那么JIoEndpoint的init方法(见代码清单9)将创建ServerSocketFactory。当SSLEnabled="true"时,JIoEndpoint的init方法还会给ServerSocketFactory设置一些SSL相关的属性。最后使用此ServerSocketFactory创建serverSocket。此外,acceptorThreadCount属性用于指定接受连接的线程数,可以通过给Connector设置acceptorThreadCount属性进行调整,默认值为1。
代码清单9

[java]  view plain  copy
 
  1. @Override  
  2. public void init()  
  3.     throws Exception {  
  4.   
  5.     if (initialized)  
  6.         return;  
  7.       
  8.     // Initialize thread count defaults for acceptor  
  9.     if (acceptorThreadCount == 0) {  
  10.         acceptorThreadCount = 1;  
  11.     }  
  12.     if (serverSocketFactory == null) {  
  13.         serverSocketFactory = ServerSocketFactory.getDefault();  
  14.     }  
  15.     if (isSSLEnabled()) {  
  16.         serverSocketFactory.setAttribute(SSL_ATTR_ALGORITHM,  
  17.                 getAlgorithm());  
  18.         serverSocketFactory.setAttribute(SSL_ATTR_CLIENT_AUTH,  
  19.                 getClientAuth());  
  20.         serverSocketFactory.setAttribute(SSL_ATTR_KEYSTORE_FILE,  
  21.                 getKeystoreFile());  
  22.         serverSocketFactory.setAttribute(SSL_ATTR_KEYSTORE_PASS,  
  23.                 getKeystorePass());  
  24.         serverSocketFactory.setAttribute(SSL_ATTR_KEYSTORE_TYPE,  
  25.                 getKeystoreType());  
  26.         serverSocketFactory.setAttribute(SSL_ATTR_KEYSTORE_PROVIDER,  
  27.                 getKeystoreProvider());  
  28.         serverSocketFactory.setAttribute(SSL_ATTR_SSL_PROTOCOL,  
  29.                 getSslProtocol());  
  30.         serverSocketFactory.setAttribute(SSL_ATTR_CIPHERS,  
  31.                 getCiphers());  
  32.         serverSocketFactory.setAttribute(SSL_ATTR_KEY_ALIAS,  
  33.                 getKeyAlias());  
  34.         serverSocketFactory.setAttribute(SSL_ATTR_KEY_PASS,  
  35.                 getKeyPass());  
  36.         serverSocketFactory.setAttribute(SSL_ATTR_TRUSTSTORE_FILE,  
  37.                 getTruststoreFile());  
  38.         serverSocketFactory.setAttribute(SSL_ATTR_TRUSTSTORE_PASS,  
  39.                 getTruststorePass());  
  40.         serverSocketFactory.setAttribute(SSL_ATTR_TRUSTSTORE_TYPE,  
  41.                 getTruststoreType());  
  42.         serverSocketFactory.setAttribute(SSL_ATTR_TRUSTSTORE_PROVIDER,  
  43.                 getTruststoreProvider());  
  44.         serverSocketFactory.setAttribute(SSL_ATTR_TRUSTSTORE_ALGORITHM,  
  45.                 getTruststoreAlgorithm());  
  46.         serverSocketFactory.setAttribute(SSL_ATTR_CRL_FILE,  
  47.                 getCrlFile());  
  48.         serverSocketFactory.setAttribute(SSL_ATTR_TRUST_MAX_CERT_LENGTH,  
  49.                 getTrustMaxCertLength());  
  50.         serverSocketFactory.setAttribute(SSL_ATTR_SESSION_CACHE_SIZE,  
  51.                 getSessionCacheSize());  
  52.         serverSocketFactory.setAttribute(SSL_ATTR_SESSION_TIMEOUT,  
  53.                 getSessionTimeout());  
  54.         serverSocketFactory.setAttribute(SSL_ATTR_ALLOW_UNSAFE_RENEG,  
  55.                 getAllowUnsafeLegacyRenegotiation());  
  56.     }  
  57.   
  58.     if (serverSocket == null) {  
  59.         try {  
  60.             if (getAddress() == null) {  
  61.                 serverSocket = serverSocketFactory.createSocket(getPort(), getBacklog());  
  62.             } else {  
  63.                 serverSocket = serverSocketFactory.createSocket(getPort(), getBacklog(), getAddress());  
  64.             }  
  65.         } catch (BindException orig) {  
  66.             String msg;  
  67.             if (getAddress() == null)  
  68.                 msg = orig.getMessage() + :" + getPort();  
  69.             else  
  70.                 msg = orig.getMessage() + " " +  
  71.                         getAddress().toString() + ":" + getPort();  
  72.             BindException be = new BindException(msg);  
  73.             be.initCause(orig);  
  74.             throw be;  
  75.         }  
  76.     }  
  77.     //if( serverTimeout >= 0 )  
  78.     //    serverSocket.setSoTimeout( serverTimeout );  
  79.       
  80.     initialized = true;  
  81.       
  82. }  

启动ProtocolHandler

  我们继续以Http11Protocol为例,剖析ProtocolHandler的start方法,其实现见代码清单10。

代码清单10

[java]  view plain  copy
 
  1. @Override  
  2. public void start() throws Exception {  
  3.     if (this.domain != null) {  
  4.         try {  
  5.             tpOname = new ObjectName  
  6.                 (domain + ":" + "type=ThreadPool,name=" + getName());  
  7.             Registry.getRegistry(nullnull)  
  8.                 .registerComponent(endpoint, tpOname, null );  
  9.         } catch (Exception e) {  
  10.             log.error("Can't register endpoint");  
  11.         }  
  12.         rgOname=new ObjectName  
  13.             (domain + ":type=GlobalRequestProcessor,name=" + getName());  
  14.         Registry.getRegistry(nullnull).registerComponent  
  15.             ( cHandler.global, rgOname, null );  
  16.     }  
  17.   
  18.     try {  
  19.         endpoint.start();  
  20.     } catch (Exception ex) {  
  21.         log.error(sm.getString("http11protocol.endpoint.starterror"), ex);  
  22.         throw ex;  
  23.     }  
  24.     if (log.isInfoEnabled())  
  25.         log.info(sm.getString("http11protocol.start", getName()));  
  26. }  

从代码清单10可以知道JIoEndpoint以Catalina:type=ThreadPool,name=http-8080注册到JMX,cHandler.global(Http11ConnectionHandler的对象属性,类型为RequestGroupInfo)以Catalina:type=GlobalRequestProcessor,name=http-8080注册到JMX。最后调用JIoEndpoint的start方法(见代码清单11)接受请求的创建线程池并创建一定数量的接收请求线程。

代码清单11

[java]  view plain  copy
 
  1. @Override  
  2. public void start() throws Exception {  
  3.     // Initialize socket if not done before  
  4.     if (!initialized) {  
  5.         init();  
  6.     }  
  7.     if (!running) {  
  8.         running = true;  
  9.         paused = false;  
  10.   
  11.         // Create worker collection  
  12.         if (getExecutor() == null) {  
  13.             createExecutor();  
  14.         }  
  15.   
  16.         // Start acceptor threads  
  17.         for (int i = 0; i < acceptorThreadCount; i++) {  
  18.             Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i);  
  19.             acceptorThread.setPriority(threadPriority);  
  20.             acceptorThread.setDaemon(getDaemon());  
  21.             acceptorThread.start();  
  22.         }  
  23.     }  
  24. }  

从代码清单11看出JIoEndpoint的start方法的执行步骤如下:

步骤一 对JIoEndpoint做初始化检查

  这一步实际就是判断是否已经初始化(即initialized是否为true),如果没有初始化则需要调用JIoEndpoint的init方法进行初始化。

步骤二 创建线程池与任务队列

  如果JIoEndpoint尚未处于运行中(即running等于true),才会创建线程池和任务队列。如果尚未创建线程池(即调用getExecutor方法等于null),则需要调用createExecutor方法(见代码清单12)创建线程池和任务队列TaskQueue。

代码清单12

[java]  view plain  copy
 
  1. public void createExecutor() {  
  2.     internalExecutor = true;  
  3.     TaskQueue taskqueue = new TaskQueue();  
  4.     TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());  
  5.     executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);  
  6.     taskqueue.setParent( (ThreadPoolExecutor) executor);  
  7. }  

步骤三 创建接收请线程

  如果JIoEndpoint尚未处于运行中(即running等于true),才会创建接收请求线程。从代码清单11可以看出接收请求线程的数量主要由acceptorThreadCount控制,代码清单9已经告诉我们acceptorThreadCount的默认值为1,但是我们可以通过给Connector增加acceptorThreadCount属性来修改接收请求线程的数量。这些接收请求线程的主要工作由Acceptor完成,Acceptor的实质是一个Runnable,见代码清单13。

代码清单13

[java]  view plain  copy
 
  1. /** 
  2.  * Server socket acceptor thread. 
  3.  */  
  4. protected class Acceptor implements Runnable {  
  5.   
  6.   
  7.     /** 
  8.      * The background thread that listens for incoming TCP/IP connections and 
  9.      * hands them off to an appropriate processor. gja 
  10.      */  
  11.     public void run() {  
  12.   
  13.         // Loop until we receive a shutdown command  
  14.         while (running) {  
  15.   
  16.             // Loop if endpoint is paused  
  17.             while (paused) {  
  18.                 try {  
  19.                     Thread.sleep(1000);  
  20.                 } catch (InterruptedException e) {  
  21.                     // Ignore  
  22.                 }  
  23.             }  
  24.   
  25.             // Accept the next incoming connection from the server socket  
  26.             try {  
  27.                 Socket socket = serverSocketFactory.acceptSocket(serverSocket);  
  28.                 serverSocketFactory.initSocket(socket);  
  29.                 // Hand this socket off to an appropriate processor  
  30.                 if (!processSocket(socket)) {  
  31.                     // Close socket right away  
  32.                     try {  
  33.                         socket.close();  
  34.                     } catch (IOException e) {  
  35.                         // Ignore  
  36.                     }  
  37.                 }  
  38.             }catch ( IOException x ) {  
  39.                 if ( running ) log.error(sm.getString("endpoint.accept.fail"), x);  
  40.             } catch (Throwable t) {  
  41.                 log.error(sm.getString("endpoint.accept.fail"), t);  
  42.             }  
  43.   
  44.             // The processor will recycle itself when it finishes  
  45.   
  46.         }  
  47.   
  48.     }  
  49.   
  50. }  

初始化MapperListener

  MapperListener的init方法用于初始化,见代码清单14。

代码清单14

[java]  view plain  copy
 
  1. /** 
  2.  * Initialize associated mapper. 
  3.  */  
  4. public void init() {  
  5.   
  6.     // Find any components that have already been initialized since the  
  7.     // MBean listener won't be notified as those components will have  
  8.     // already registered their MBeans jiaan  
  9.     findDefaultHost();  
  10.       
  11.     Engine engine = (Engine) connector.getService().getContainer();  
  12.     engine.addContainerListener(this);  
  13.       
  14.     Container[] conHosts = engine.findChildren();  
  15.     for (Container conHost : conHosts) {  
  16.         Host host = (Host) conHost;  
  17.         if (!LifecycleState.NEW.equals(host.getState())) {  
  18.             host.addLifecycleListener(this);  
  19.             // Registering the host will register the context and wrappers  
  20.             registerHost(host);  
  21.         }  
  22.     }  
  23. }  

从代码清单14看到MapperListener的初始化步骤如下:

步骤一 查找默认Host

  StandardService的子容器包括:StandardEngine、Connector和Executor。MapperListener本身会持有Connector,所以可以通过各个容器的父子关系,找到Connector的同级容器StandardEngine。StandardHost是StandardEngine的子容器,Engine和Host的默认配置如下:

[html]  view plain  copy
 
  1. <Engine name="Catalina" defaultHost="localhost">  
  2.   
  3.   <Realm className="org.apache.catalina.realm.UserDatabaseRealm"  
  4.          resourceName="UserDatabase"/>  
  5.   
  6.   
  7.   <Host name="localhost"  appBase="webapps"  
  8.         unpackWARs="true" autoDeploy="true">  
  9.   
  10.     <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"    
  11.            prefix="localhost_access_log." suffix=".txt"  
  12.            pattern="%h %l %u %t "%r" %s %b" resolveHosts="false"/>  
  13.   
  14.   Host>  
  15. Engine>  

findDefaultHost方法(见代码清单15)可以获取上面配置中的默认Host,Engine元素的defaultHost属性值必须要与配置的某个Host元素的name属性值相同。如果defaultHost的属性值配置无误,则会添加为MapperListener的Mapper对象属性的defaultHostName。

代码清单15

[java]  view plain  copy
 
  1. private void findDefaultHost() {  
  2.   
  3.     Engine engine = (Engine) connector.getService().getContainer();  
  4.     String defaultHost = engine.getDefaultHost();  
  5.   
  6.     boolean found = false;  
  7.   
  8.     if (defaultHost != null && defaultHost.length() >0) {  
  9.         Container[] containers = engine.findChildren();  
  10.           
  11.         for (Container container : containers) {  
  12.             Host host = (Host) container;  
  13.             if (defaultHost.equalsIgnoreCase(host.getName())) {  
  14.                 found = true;  
  15.                 break;  
  16.             }  
  17.               
  18.             String[] aliases = host.findAliases();  
  19.             for (String alias : aliases) {  
  20.                 if (defaultHost.equalsIgnoreCase(alias)) {  
  21.                     found = true;  
  22.                     break;  
  23.                 }  
  24.             }  
  25.         }  
  26.     }  
  27.   
  28.     if(found) {  
  29.         mapper.setDefaultHostName(defaultHost);  
  30.     } else {  
  31.         log.warn(sm.getString("mapperListener.unknownDefaultHost",  
  32.                 defaultHost));  
  33.     }  
  34. }  

步骤二 将Host及其子容器Context,Context的子容器Wrapper注册到MapperListener的Mapper对象

  Mapper的数据结构,见代码清单16。

代码清单16

[java]  view plain  copy
 
  1. /** 
  2.  * Array containing the virtual hosts definitions. 
  3.  */  
  4. protected Host[] hosts = new Host[0];  
  5.   
  6.   
  7. /** 
  8.  * Default host name. 
  9.  */  
  10. protected String defaultHostName = null;  
  11.   
  12. /** 
  13.  * Context associated with this wrapper, used for wrapper mapping. 
  14.  */  
  15. protected Context context = new Context();  
  16.   
  17. protected static abstract class MapElement {  
  18.   
  19.     public String name = null;  
  20.     public Object object = null;  
  21.   
  22. }  
  23.   
  24. protected static final class Host  
  25.     extends MapElement {  
  26.   
  27.     public ContextList contextList = null;  
  28.   
  29. }  
  30.   
  31. protected static final class ContextList {  
  32.   
  33.     public Context[] contexts = new Context[0];  
  34.     public int nesting = 0;  
  35.   
  36. }  
  37.   
  38. protected static final class Context  
  39.     extends MapElement {  
  40.   
  41.     public String path = null;  
  42.     public String[] welcomeResources = new String[0];  
  43.     public javax.naming.Context resources = null;  
  44.     public Wrapper defaultWrapper = null;  
  45.     public Wrapper[] exactWrappers = new Wrapper[0];  
  46.     public Wrapper[] wildcardWrappers = new Wrapper[0];  
  47.     public Wrapper[] extensionWrappers = new Wrapper[0];  
  48.     public int nesting = 0;  
  49.   
  50. }  
  51.   
  52. protected static class Wrapper  
  53.     extends MapElement {  
  54.   
  55.     public String path = null;  
  56.     public boolean jspWildCard = false;  
  57. }  

根据代码清单16,我们知道Mapper中维护着一个Host数组,每个Host中有一个ContextList,这个ContextList中维护着一个Context数组。每个Context维护着一个defaultWrapper,三个Wrapper数组(exactWrappers、wildcardWrappers、extensionWrappers)。下面对Host、Context及Wrapper进行功能上的介绍:

  • Host:代表一个虚拟主机,各Host的name不能相同,appBase代表各虚拟主机的应用发布位置;
  • Context:代表一个应用,Context可以根据应用的/WEB-INF/web.xml文件中定义的servlet来处理请求。一个Host下可以有多个Context;
  • Wrapper: 代表一个Servlet或者jsp,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。 
以我本地为例,注册到Mapper中的Host及其子容器如图2所示。


图2  注册到Mapper中的Host及其Context子容器 

图2说明Host内一共5个Context,由于我的Tomcat是从svn拉下来的,所以webapps目录下的.svn文件夹也是一个Context,除了这个天外来客,我将其它与请求有关的容器整理后用图3来展示。


图3  我本地的Host、Context及Wrapper

  至此,Tomcat中为请求处理的准备工作已经完成。有关请求的处理过程请继续阅读《Tomcat源码分析——请求原理分析(中)》一文。



后记:个人总结整理的《深入理解Spark:核心思想与源码分析》一书现在已经正式出版上市,目前京东、当当、天猫等网站均有销售,欢迎感兴趣的同学购买。


京东:http://item.jd.com/11846120.html 

当当:http://product.dangdang.com/23838168.html 

你可能感兴趣的:(TOMCAT)