Tomcat學習之Connector

目錄(?)[-]html

  1. protocol
  2. Service
  3. mapperListener
  4. 初始化與啓動

以下圖所示,Tomcat服務器主要有兩大核心模塊組成:鏈接器和容器,本節只分析鏈接器的實現。java

鏈接器主要是接收用戶的請求,而後封裝請求傳遞給容器處理,tomcat中默認的鏈接器是Coyote.首先來看鏈接器的類圖:web

protocol

咱們發現這個類裏面有不少與protocol有關的屬性和方法,tomcat中支持兩種協議的鏈接器:HTTP/1.1與AJP/1.3,查看tomcat的配置文件server.xml能夠看到以下配置:apache

[java] view plain copy瀏覽器

 print?tomcat

  1. <Connector port="8080" protocol="HTTP/1.1"   
  2.                connectionTimeout="20000"   
  3.                redirectPort="8443" URIEncoding="utf-8"/>  
  4.   
  5. <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />  

HTTP/1.1協議負責創建HTTP鏈接,web應用經過瀏覽器訪問tomcat服務器用的就是這個鏈接器,默認監聽的是8080端口;服務器

AJP/1.3協議負責和其餘HTTP服務器創建鏈接,監聽的是8009端口,好比tomcat和apache或者iis集成時須要用到這個鏈接器。網絡

協議上有三種不一樣的實現方式:JIO、APR、NIO。app

JIO(java.io):用java.io純JAVA編寫的TCP模塊,這是tomcat默認鏈接器實現方法;異步

APR(Apache Portable Runtime):有C語言和JAVA兩種語言實現,鏈接Apache httpd Web服務器的類庫是在C中實現的,同時用APR進行網絡通訊;

NIO(java.nio):這是用純Java編寫的鏈接器(Conector)的一種可選方法。該實現用java.nio核心Java網絡類以提供非阻塞的TCP包特性。

ProtocolHandler接口是對這些協議的抽象,其類層次結構圖以下呼所示:

前面提到tomcat默認採用的是Http11Protocol,要麼要怎麼更換默認的protocolHandler呢,先看看setProtocol方法的源碼:

[java] view plain copy

 print?

  1. public void setProtocol(String protocol) {  
  2.   
  3.     if (AprLifecycleListener.isAprAvailable()) {  
  4.         if ("HTTP/1.1".equals(protocol)) {  
  5.             setProtocolHandlerClassName  
  6.                 ("org.apache.coyote.http11.Http11AprProtocol");  
  7.         } else if ("AJP/1.3".equals(protocol)) {  
  8.             setProtocolHandlerClassName  
  9.                 ("org.apache.coyote.ajp.AjpAprProtocol");  
  10.         } else if (protocol != null) {  
  11.             setProtocolHandlerClassName(protocol);  
  12.         } else {  
  13.             setProtocolHandlerClassName  
  14.                 ("org.apache.coyote.http11.Http11AprProtocol");  
  15.         }  
  16.     } else {  
  17.         if ("HTTP/1.1".equals(protocol)) {  
  18.             setProtocolHandlerClassName  
  19.                 ("org.apache.coyote.http11.Http11Protocol");  
  20.         } else if ("AJP/1.3".equals(protocol)) {  
  21.             setProtocolHandlerClassName  
  22.                 ("org.apache.coyote.ajp.AjpProtocol");  
  23.         } else if (protocol != null) {  
  24.             setProtocolHandlerClassName(protocol);  
  25.         }  
  26.     }  
  27. }  

從以上代碼能夠看出只要protocol不爲HTTP/1.1也不爲AJP/1.3就會調用setProtocolHandlerClassName來設置protocolHandler,也就是說要替換默認的protocolHandler,只須要修改server.xml文件Connector中的protocol屬性便可!

 

 

Service

鏈接器和容器一塊兒才能對外提供服務,Service裏面包含了一個容器和多個鏈接器,鏈接器是怎麼加入到Service中的,能夠看一下StandardService中的代碼:

[html] view plain copy

 print?

  1. public void addConnector(Connector connector) {  
  2.   
  3.     synchronized (connectors) {  
  4.         connector.setService(this);  
  5.         Connector results[] = new Connector[connectors.length + 1];  
  6.         System.arraycopy(connectors, 0, results, 0, connectors.length);  
  7.         results[connectors.length] = connector;  
  8.         connectors = results;  
  9.   
  10.         if (getState().isAvailable()) {  
  11.             try {  
  12.                 connector.start();  
  13.             } catch (LifecycleException e) {  
  14.                 log.error(sm.getString(  
  15.                         "standardService.connector.startFailed",  
  16.                         connector), e);  
  17.             }  
  18.         }  
  19.   
  20.         // Report this property change to interested listeners  
  21.         support.firePropertyChange("connector", null, connector);  
  22.     }  
  23.   
  24. }  

StandardService中還有setContainer方法,正是Service把容器和鏈接器關聯在一塊兒的

mapperListener

在鏈接器初始化的時候會初始化mapperListener,mapperListener在初始化的時候會調用mapper對象的addXXX方法

Mapper對象在tomcat中存在於兩個地方:

一、每一個context容器對象中,它只記錄了此context內部的訪問資源與相對應的wrapper子容器的映射;

二、connector模塊中,這是tomcat全局的變量,它記錄了一個完整的映射對應關係,即根據訪問的完整URL如何定位到哪一個host下的哪一個context的哪一個wrapper容器。
Servlet中forward跳轉會用到第一種mapper,也就是說forward是服務器內部的重定向。

初始化與啓動

Connector的初始化過程以下:

一、Tomcat初始化時會調用Bootstrap的Load方法,這裏會解析XML文件,Digester解析的過程當中,會調用Connector的構造方法

[html] view plain copy

 print?

  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(sm.getString(  
  9.                    "coyoteConnector.protocolHandlerInstantiationFailed"), e);  
  10.        }  
  11.    }  

這個構造方法首先設置protocol,而後初始化protocolhandler

二、緊接着初始化Server,一個Server可能有多個Service,在初始化Server的過程當中會初始化多個Service.Service由一個容器和多個鏈接器組成,會先初始化容器,而後初始化化兩個鏈接器,鏈接器的初始化是調用的Connector的initInternal方法

[html] view plain copy

 print?

  1. protected void initInternal() throws LifecycleException {  
  2.   
  3.     super.initInternal();  
  4.   
  5.     // Initialize adapter  
  6.     adapter = new CoyoteAdapter(this);  
  7.     protocolHandler.setAdapter(adapter);  
  8.   
  9.     // Make sure parseBodyMethodsSet has a default  
  10.     if( null == parseBodyMethodsSet ) {  
  11.         setParseBodyMethods(getParseBodyMethods());  
  12.     }  
  13.   
  14.     try {  
  15.         protocolHandler.init();  
  16.     } catch (Exception e) {  
  17.         throw new LifecycleException  
  18.             (sm.getString  
  19.              ("coyoteConnector.protocolHandlerInitializationFailed"), e);  
  20.     }  
  21.   
  22.     // Initialize mapper listener  
  23.     mapperListener.init();  
  24. }  

(1)首先爲protocolHandler設置一個adapter,而後初始化protocolHandler

(2)初始化mapperListener,啥都沒作,只是調用了父類(LifecycleMBeanBase)的initInternal方法!
因爲Tomcat的生命週期控制,鏈接器的啓動過程和初始化過程幾乎同樣,也是由Catalina的start方法開始,Server啓動,Service啓動,Container啓動,Connector啓動。Connector啓動調用了它的startInternal方法,這個方法只作了兩件事:啓動protocolHandler和mapperListener,鏈接器的啓動過程就是這樣!下面分別來看看protocolHandler和mapperListener啓動時都作了哪些事?

protocolHandler的初始化是在其父類AbstractProtocol的start方法中完成的,

[html] view plain copy

 print?

  1. public void start() throws Exception {  
  2.     if (getLog().isInfoEnabled())  
  3.         getLog().info(sm.getString("abstractProtocolHandler.start",  
  4.                 getName()));  
  5.     try {  
  6.         endpoint.start();  
  7.     } catch (Exception ex) {  
  8.         getLog().error(sm.getString("abstractProtocolHandler.startError",  
  9.                 getName()), ex);  
  10.         throw ex;  
  11.     }  
  12. }  

調用了endpoint的start方法,這個方法裏面作了如下幾件事:

(1)設置接收線程數和最大鏈接數,建立socket

(2)建立線程池,啓動監聽的線程監聽用戶請求

(3)啓動一個線程來負責異步請求

mapperListener也繼承了LifecycleMBeanBase類,也是受生命週期控制的。因此它的啓動是在startInternal方法中完成的

[html] view plain copy

 print?

  1. public void startInternal() throws LifecycleException {  
  2.   
  3.        setState(LifecycleState.STARTING);  
  4.   
  5.        // Find any components that have already been initialized since the  
  6.        // MBean listener won't be notified as those components will have  
  7.        // already registered their MBeans  
  8.        findDefaultHost();  
  9.   
  10.        Engine engine = (Engine) connector.getService().getContainer();  
  11.        addListeners(engine);  
  12.   
  13.        Container[] conHosts = engine.findChildren();  
  14.        for (Container conHost : conHosts) {  
  15.            Host host = (Host) conHost;  
  16.            if (!LifecycleState.NEW.equals(host.getState())) {  
  17.                // Registering the host will register the context and wrappers  
  18.                registerHost(host);  
  19.            }  
  20.        }  
  21.    }  

(1)註冊已初始化的組件

(2)爲各類容器添加監聽器

(3)爲各類容器創建映射關係

相關文章
相關標籤/搜索