zoukankan      html  css  js  c++  java
  • 探秘Tomcat——连接器和容器的优雅启动

    前言:

      上篇《探秘Tomcat——启动篇》粗线条的介绍了在tomcat在启动过程中如何初始化Bootstrap类,加载并执行server,从而启动整个tomcat服务,一直到我们看到控制台打印出如下信息

    七月 16, 2016 4:42:18 下午 org.apache.catalina.core.AprLifecycleListener init
    信息: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: C:Program FilesJavajdk1.8.0_60in;C:WindowsSunJavain;C:Windowssystem32;C:Windows;C:Program FilesJavajdk1.8.0_60jrein;C:/Program Files/Java/jdk1.8.0_60/bin/../jre/bin/server;C:/Program Files/Java/jdk1.8.0_60/bin/../jre/bin;C:/Program Files/Java/jdk1.8.0_60/bin/../jre/lib/amd64;C:Program FilesJavajdk1.8.0_60in;C:Windowssystem32;C:Windows;C:WindowsSystem32Wbem;C:WindowsSystem32WindowsPowerShellv1.0;C:Program Files (x86)ATI TechnologiesATI.ACECore-Static;C:Program Files (x86)IntelOpenCL SDK2.0inx86;C:Program Files (x86)IntelOpenCL SDK2.0inx64;C:Program Files (x86)Microsoft SQL Server100ToolsBinn;C:Program FilesMicrosoft SQL Server100ToolsBinn;C:Program FilesMicrosoft SQL Server100DTSBinn;C:Program Files (x86)Microsoft SQL Server100ToolsBinnVSShellCommon7IDE;C:Program Files (x86)Microsoft Visual Studio 9.0Common7IDEPrivateAssemblies;C:Program Files (x86)Microsoft SQL Server100DTSBinn;C:Program Files
    odejs;E:softwareapache-maven-3.1.0-binapache-maven-3.1.0in;E:softwaregradle-2.7in;C:Program Files (x86)Gitin;C:Program Files (x86)Gitcmd;C:UsersAdministratorDesktop博客20160410androidandroid-sdk-windows	ools;E:softwareapache-ant-1.9.7-binapache-ant-1.9.7in;C:UsersAdministratorAppDataRoaming
    pm;E:安装包学习软件eclipse-jee-mars-1-win32-x86_64eclipse;;.
    七月 16, 2016 4:42:41 下午 org.apache.coyote.http11.Http11Protocol init
    信息: Initializing Coyote HTTP/1.1 on http-8080
    七月 16, 2016 4:45:01 下午 org.apache.catalina.startup.Catalina load
    信息: Initialization processed in 190850 ms
    七月 16, 2016 4:45:07 下午 org.apache.catalina.core.StandardService start
    信息: Starting service Catalina
    七月 16, 2016 4:45:07 下午 org.apache.catalina.core.StandardEngine start
    信息: Starting Servlet Engine: Apache Tomcat/@VERSION@
    七月 16, 2016 4:45:12 下午 org.apache.catalina.startup.HostConfig deployDescriptor
    信息: Deploying configuration descriptor host-manager.xml
    七月 16, 2016 4:45:17 下午 org.apache.catalina.startup.HostConfig deployDescriptor
    信息: Deploying configuration descriptor manager.xml
    七月 16, 2016 4:45:18 下午 org.apache.catalina.startup.HostConfig deployDirectory
    信息: Deploying web application directory docs
    七月 16, 2016 4:45:19 下午 org.apache.catalina.startup.HostConfig deployDirectory
    信息: Deploying web application directory examples
    七月 16, 2016 4:45:21 下午 org.apache.catalina.core.ApplicationContext log
    信息: ContextListener: contextInitialized()
    七月 16, 2016 4:45:21 下午 org.apache.catalina.core.ApplicationContext log
    信息: SessionListener: contextInitialized()
    七月 16, 2016 4:45:21 下午 org.apache.catalina.startup.HostConfig deployDirectory
    信息: Deploying web application directory ROOT
    七月 16, 2016 4:47:47 下午 org.apache.coyote.http11.Http11Protocol start
    信息: Starting Coyote HTTP/1.1 on http-8080
    七月 16, 2016 4:48:36 下午 org.apache.jk.common.ChannelSocket init
    信息: JK: ajp13 listening on /0.0.0.0:8009
    七月 16, 2016 4:48:44 下午 org.apache.jk.server.JkMain start
    信息: Jk running ID=0 time=7967/16219  config=null
    七月 16, 2016 4:49:07 下午 org.apache.catalina.startup.Catalina start
    信息: Server startup in 243017 ms

      表示tomcat服务启动成功。

      从上面的tomcat启动过程打印信息我们可以发现,在启动tomcat时,我们做了很多工作,包括一些类加载器的初始化,server的加载和启动等,本篇紧接着上篇来说说

    七月 16, 2016 4:47:47 下午 org.apache.coyote.http11.Http11Protocol start
    信息: Starting Coyote HTTP/1.1 on http-8080
    七月 16, 2016 4:48:36 下午 org.apache.jk.common.ChannelSocket init
    信息: JK: ajp13 listening on /0.0.0.0:8009
    七月 16, 2016 4:48:44 下午 org.apache.jk.server.JkMain start
    信息: Jk running ID=0 time=7967/16219  config=null

      这几行console信息背后的故事……

    正文:

      我们还是从Bootstrap类的main方法说起

     1 public static void main(String args[]) {
     2 
     3         if (daemon == null) {
     4             daemon = new Bootstrap();
     5             try {
     6                 daemon.init();
     7             } catch (Throwable t) {
     8                 t.printStackTrace();
     9                 return;
    10             }
    11         }
    12 
    13         try {
    14             String command = "start";
    15             if (args.length > 0) {
    16                 command = args[args.length - 1];
    17             }
    18 
    19             if (command.equals("startd")) {
    20                 args[args.length - 1] = "start";
    21                 daemon.load(args);
    22                 daemon.start();
    23             } else if (command.equals("stopd")) {
    24                 args[args.length - 1] = "stop";
    25                 daemon.stop();
    26             } else if (command.equals("start")) {
    27                 daemon.setAwait(true);
    28                 daemon.load(args);
    29                 daemon.start();
    30             } else if (command.equals("stop")) {
    31                 daemon.stopServer(args);
    32             } else {
    33                 log.warn("Bootstrap: command "" + command + "" does not exist.");
    34             }
    35         } catch (Throwable t) {
    36             t.printStackTrace();
    37         }
    38 
    39     }

    在line28~29可以看出依次执行deamon的load和start方法,而实际上这两个方法的具体实现是通过反射机制跳转到类Catalina中找到相应的load和start方法的。

    load方法执行的是谁的load?load了那些服务组件?load的目的又是什么?

      Catalina.load方法中一个很重要的方法就是createStartDigester,完成的工作是根据conf/server.xml文件中的数据,将相应的元素转化 为对象,将元素中的属性转化为生成对象的属性,并且理清楚各个元素之间的关联关系。比如server.xml文件中最外层的元素是server,server中包含了子节点service,而在这个service里面又有很多元素节点如Connector、Engie、Host等等,这是他们之间的关系。简单说就是先定义一个规则,好让后面在实际解析这个xml文件的时候有章可循。

      当在执行到load中的digester.parse(inputSource)方法时,会依次遍历每个元素,当遍历到Connector元素的时候,会依次调用Digester.startElement->Rule.begin->ConnectorCreateRule.begin.

     1 public void begin(Attributes attributes) throws Exception {
     2         Service svc = (Service)digester.peek();
     3         Executor ex = null;
     4         if ( attributes.getValue("executor")!=null ) {
     5             ex = svc.getExecutor(attributes.getValue("executor"));
     6         }
     7         Connector con = new Connector(attributes.getValue("protocol"));
     8         if ( ex != null )  _setExecutor(con,ex);
     9         
    10         digester.push(con);
    11     }

      line7获取到server.xml中Connector的protocol属性之后,以此传值并创建一个Connetor对象。

      备注:server.xml中有声明了两个Connetor元素,分别是:

     1 <!-- A "Connector" represents an endpoint by which requests are received
     2          and responses are returned. Documentation at :
     3          Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)
     4          Java AJP  Connector: /docs/config/ajp.html
     5          APR (HTTP/AJP) Connector: /docs/apr.html
     6          Define a non-SSL HTTP/1.1 Connector on port 8080
     7     -->
     8     <Connector port="8080" protocol="HTTP/1.1" 
     9                connectionTimeout="20000" 
    10                redirectPort="8443" />
    11     <!-- A "Connector" using the shared thread pool-->
    12     <!--
    13     <Connector executor="tomcatThreadPool"
    14                port="8080" protocol="HTTP/1.1" 
    15                connectionTimeout="20000" 
    16                redirectPort="8443" />
    17     -->           
    18     <!-- Define a SSL HTTP/1.1 Connector on port 8443
    19          This connector uses the JSSE configuration, when using APR, the 
    20          connector should be using the OpenSSL style configuration
    21          described in the APR documentation -->
    22     <!--
    23     <Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
    24                maxThreads="150" scheme="https" secure="true"
    25                clientAuth="false" sslProtocol="TLS" />
    26     -->
    27 
    28     <!-- Define an AJP 1.3 Connector on port 8009 -->
    29     <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
    server.xml-Connetor

      从Connetor类的构造函数可以看出,我们首先会执行Connetor类的setProtocol方法,这时候传入的attributs.getValue("protocol")就会派上用场。

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

      setProtocol方法如下

     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.jk.server.JkCoyoteHandler");
    23             } else if (protocol != null) {
    24                 setProtocolHandlerClassName(protocol);
    25             }
    26         }
    27 
    28     }

      这里首先遍历到的server.xml中的Connector元素是protocol="HTTP/1.1",这时候将org.apache.coyote.http11.Http11Protocol赋值给Connetor的protocolHandlerClassName变量,之后在Connetor构造函数中完成以当前的protocolHandlerClassName值构造一个org.apache.coyote.http11.Http11Protocol对象,并赋值于Connetor的protocolHandler变量。在Http11Protocol类中我们可以发现其中的构造函数和声明的fields如下:

     1 // ------------------------------------------------------------ Constructor
     2 
     3     public Http11Protocol() {
     4         setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
     5         setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
     6         //setServerSoTimeout(Constants.DEFAULT_SERVER_SOCKET_TIMEOUT);
     7         setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
     8     }
     9 
    10 
    11     // ----------------------------------------------------------------- Fields
    12 
    13     protected Http11ConnectionHandler cHandler = new Http11ConnectionHandler(this);
    14     protected JIoEndpoint endpoint = new JIoEndpoint();
    View Code

      这里初始化主要用于创建serviceSocket对象

      这里的protocolHandler.init()会根据当前的protocolHandler的对象调用相应类的init方法,比如对于Http11Protocol,则会调用Http11Protocol中的init方法,而Http11Protocol.init又会调用endpiont.init方法,endpiont.init的具体实现在JIoEndpoint的init方法中,如下:

     1 public void init()
     2         throws Exception {
     3 
     4         if (initialized)
     5             return;
     6         
     7         // Initialize thread count defaults for acceptor
     8         if (acceptorThreadCount == 0) {
     9             acceptorThreadCount = 1;
    10         }
    11         if (serverSocketFactory == null) {
    12             serverSocketFactory = ServerSocketFactory.getDefault();
    13         }
    14         if (serverSocket == null) {
    15             try {
    16                 if (address == null) {
    17                     serverSocket = serverSocketFactory.createSocket(port, backlog);
    18                 } else {
    19                     serverSocket = serverSocketFactory.createSocket(port, backlog, address);
    20                 }
    21             } catch (BindException orig) {
    22                 String msg;
    23                 if (address == null)
    24                     msg = orig.getMessage() + " <null>:" + port;
    25                 else
    26                     msg = orig.getMessage() + " " +
    27                             address.toString() + ":" + port;
    28                 BindException be = new BindException(msg);
    29                 be.initCause(orig);
    30                 throw be;
    31             }
    32         }
    33         //if( serverTimeout >= 0 )
    34         //    serverSocket.setSoTimeout( serverTimeout );
    35         
    36         initialized = true;
    37         
    38     }

      line17创建了serverSocket对象(这里的调用关系比较深,要结合代码和debug来看)。

      当Http11Protocol.init方法执行完后,console会打印如下信息:

    七月 16, 2016 7:03:06 下午 org.apache.coyote.http11.Http11Protocol init
    信息: Initializing Coyote HTTP/1.1 on http-8080

      之后同理解析到"AJP/1.3"并生成JkCoyoteHandler对象并完成初始化的过程。

      至此,就执行完成了load的所有工作。

    start方法又是谁的start?谁为start提供了如此便捷的实现?start又启动了那些服务组件?

      下面就开始执行我们的start方法,也就是Catalina.start。

     1 public void start() {
     2 
     3         if (getServer() == null) {
     4             load();
     5         }
     6 
     7         if (getServer() == null) {
     8             log.fatal("Cannot start server. Server instance is not configured.");
     9             return;
    10         }
    11 
    12         long t1 = System.nanoTime();
    13         
    14         // Start the new server
    15         if (getServer() instanceof Lifecycle) {
    16             try {
    17                 ((Lifecycle) getServer()).start();
    18             } catch (LifecycleException e) {
    19                 log.error("Catalina.start: ", e);
    20             }
    21         }
    22 
    23         long t2 = System.nanoTime();
    24         if(log.isInfoEnabled())
    25             log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
    26 
    27         try {
    28             // Register shutdown hook
    29             if (useShutdownHook) {
    30                 if (shutdownHook == null) {
    31                     shutdownHook = new CatalinaShutdownHook();
    32                 }
    33                 Runtime.getRuntime().addShutdownHook(shutdownHook);
    34                 
    35                 // If JULI is being used, disable JULI's shutdown hook since
    36                 // shutdown hooks run in parallel and log messages may be lost
    37                 // if JULI's hook completes before the CatalinaShutdownHook()
    38                 LogManager logManager = LogManager.getLogManager();
    39                 if (logManager instanceof ClassLoaderLogManager) {
    40                     ((ClassLoaderLogManager) logManager).setUseShutdownHook(
    41                             false);
    42                 }
    43             }
    44         } catch (Throwable t) {
    45             // This will fail on JDK 1.2. Ignoring, as Tomcat can run
    46             // fine without the shutdown hook.
    47         }
    48 
    49         if (await) {
    50             await();
    51             stop();
    52         }
    53 
    54     }
    Catalina.start

      首先执行到((Lifecycle) getServer()).start()的时候会进入StandarServer执行start方法。

     1 public void start() throws LifecycleException {
     2 
     3         // Validate and update our current component state
     4         if (started) {
     5             log.debug(sm.getString("standardServer.start.started"));
     6             return;
     7         }
     8 
     9         // Notify our interested LifecycleListeners
    10         lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
    11 
    12         lifecycle.fireLifecycleEvent(START_EVENT, null);
    13         started = true;
    14 
    15         // Start our defined Services
    16         synchronized (services) {
    17             for (int i = 0; i < services.length; i++) {
    18                 if (services[i] instanceof Lifecycle)
    19                     ((Lifecycle) services[i]).start();
    20             }
    21         }
    22 
    23         // Notify our interested LifecycleListeners
    24         lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
    25 
    26     }
    • 该方法唤醒所有LifecycleListeners,具体实现在LifeCycleSupport.fireLifecycleEvent中,包括NamingContextListener、AprLifecycleListener、JasperListener、JreMemoryLeakPreventionListener、ServerLifecycleListener和GlobalResourcesLifecycleListener。
    • 通过循环遍历,启动所有的serivces。这里我们看看StandardService的start方法实现:
     1 public void start() throws LifecycleException {
     2 
     3         // Validate and update our current component state
     4         if (started) {
     5             if (log.isInfoEnabled()) {
     6                 log.info(sm.getString("standardService.start.started"));
     7             }
     8             return;
     9         }
    10         
    11         if( ! initialized )
    12             init(); 
    13 
    14         // Notify our interested LifecycleListeners
    15         lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
    16         if(log.isInfoEnabled())
    17             log.info(sm.getString("standardService.start.name", this.name));
    18         lifecycle.fireLifecycleEvent(START_EVENT, null);
    19         started = true;
    20 
    21         // Start our defined Container first
    22         if (container != null) {
    23             synchronized (container) {
    24                 if (container instanceof Lifecycle) {
    25                     ((Lifecycle) container).start();
    26                 }
    27             }
    28         }
    29 
    30         synchronized (executors) {
    31             for ( int i=0; i<executors.size(); i++ ) {
    32                 executors.get(i).start();
    33             }
    34         }
    35 
    36         // Start our defined Connectors second
    37         synchronized (connectors) {
    38             for (int i = 0; i < connectors.length; i++) {
    39                 try {
    40                     ((Lifecycle) connectors[i]).start();
    41                 } catch (Exception e) {
    42                     log.error(sm.getString(
    43                             "standardService.connector.startFailed",
    44                             connectors[i]), e);
    45                 }
    46             }
    47         }
    48         
    49         // Notify our interested LifecycleListeners
    50         lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
    51 
    52     }
    • line21~28用于递归启动Containers,大致的调用层次为:大致为Server.start->Service.start->StandarEngine.start->StandardHost.start->StandardPipeline.start
    • line36~47用于启动Connetors,即如下图所示的两个connetors:

      这里对于Http11Protocol的调用顺序是StandardService.start->Connetor.start->Http11Protocol.start->JIoEndpoint.start,启动成功后在console得到打印信息:

    1 七月 16, 2016 7:30:50 下午 org.apache.coyote.http11.Http11Protocol start
    2 信息: Starting Coyote HTTP/1.1 on http-8080

      对于JkCoyoteHandler调用顺序是StandardService.start->Connetor.start->JkCoyoteHandler.start->JkMain.start,启动成功后在console得到打印信息:

    1 七月 16, 2016 7:36:00 下午 org.apache.jk.common.ChannelSocket init
    2 信息: JK: ajp13 listening on /0.0.0.0:8009
    3 七月 16, 2016 7:36:16 下午 org.apache.jk.server.JkMain start
    4 信息: Jk running ID=0 time=33100/45405  config=null

      至此,我们算是理清楚了,如何从一个server的load和start能够把所有的services启动,以及service中的Connetor和Container启动起来的。

      其实读tomcat的代码还是很费劲的,主要的自己的功力还比较浅,其中用到的一些框架技术或者设计模式不能完全理解,所以阅读过程中会经常卡住,但是从这块启动来看,主要的脉络还是看明白了,读完之后体会还是蛮深刻:

      •   为什么tomcat能够做到启动一个server就能够把存在其上面的serveices都启动,我想这应该是得益于LifeCycle机制,正如上篇所说,所有的组件都实现了LifeCycle的接口,说白了这就是java的面向接口编程的思想的应用,每个组件都实现了LifeCycle接口,而这个接口中具有了start方法,从而可以通过递归调用实现牵一发而动全身的效果;
      •   我们对于Connetor和Container的初始化和启动的所有信息都是来源于配置文件,我们把这些可以灵活配置的信息放到了server.xml文件中,这样下次如果我们想换个端口就可以直接改在文件中,而不需要动代码,这也是降低了代码的耦合性;

      当然了,源码中的奥妙肯定远不止于此,还需要慢慢研读^_^,最近有研究tomcat源码的可以一起交流,毕竟一个人能看到的还是蛮有限的。



      如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。




    友情赞助

    如果你觉得博主的文章对你那么一点小帮助,恰巧你又有想打赏博主的小冲动,那么事不宜迟,赶紧扫一扫,小额地赞助下,攒个奶粉钱,也是让博主有动力继续努力,写出更好的文章^^。

        1. 支付宝                          2. 微信

                          

  • 相关阅读:
    asp后台读id设置样式
    js,需要更多源字符
    列名无效
    asp,对待绑定数据加序号列(DataSet)
    ashx 绝对路径得到物理路径
    方法执行一次js
    小细节
    Spring oauth大致流程
    第六章 分支语句和逻辑运算符
    第七章 函数
  • 原文地址:https://www.cnblogs.com/bigdataZJ/p/TomcatSourceZJ5.html
Copyright © 2011-2022 走看看