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. 微信

                          

  • 相关阅读:
    26 转义符 re模块 方法 random模块 collection模块的Counter方法
    25 正则表达式
    24 from 模块 import 名字
    24 from 模块 import 名字
    24 from 模块 import 名字
    23 析构方法 items系列 hash方法 eq方法
    21 isinstance issubclass 反射 _str_ _new_ _len_ _call_
    20 属性, 类方法, 静态方法. python2与python3的区别.
    python(1)
    python之字符串格式化
  • 原文地址:https://www.cnblogs.com/bigdataZJ/p/TomcatSourceZJ5.html
Copyright © 2011-2022 走看看