• Tomcat启动过程源码分析六


    前言

    上一篇文章中我们讨论了Catalina类中start方法中一部分,今天这篇文章我们把Catalina类的start方法剩余部分讲解完毕,在讲解代码之前我们先看之前的一篇关于ShutdownHook的文章,有利于后面代码的讲解。

    	/**
    	* Start a new server instance.
    	*/
    	public void start() {
    	
    	if (getServer() == null) {
    	    load();
    	}
    	
    	if (getServer() == null) {
    	    log.fatal("Cannot start server. Server instance is not configured.");
    	    return;
    	}
    	
    	long t1 = System.nanoTime();
    	
    	// Start the new server
    	try {
    	    getServer().start();
    	} catch (LifecycleException e) {
    	    log.fatal(sm.getString("catalina.serverStartFail"), e);
    	    try {
    	        getServer().destroy();
    	    } catch (LifecycleException e1) {
    	        log.debug("destroy() failed for failed Server ", e1);
    	    }
    	    return;
    	}
    	
    	long t2 = System.nanoTime();
    	if(log.isInfoEnabled()) {
    	    log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
    	}
    	//剩余部分 从这里开始
    	// Register shutdown hook
    	//1111111111111
    	if (useShutdownHook) {
    	    if (shutdownHook == null) {
    	        shutdownHook = new CatalinaShutdownHook();
    	    }
    	    Runtime.getRuntime().addShutdownHook(shutdownHook);
    	
    	    // If JULI is being used, disable JULI's shutdown hook since
    	    // shutdown hooks run in parallel and log messages may be lost
    	    // if JULI's hook completes before the CatalinaShutdownHook()
    	    LogManager logManager = LogManager.getLogManager();
    	    if (logManager instanceof ClassLoaderLogManager) {
    	        ((ClassLoaderLogManager) logManager).setUseShutdownHook(
    	                false);
    	    }
    	}
    	
    	//2222222
    	if (await) {
    	    await();
    	    stop();
    	}
    	}
    

    start方法的末尾可以看到,tomcat注册一个钩子,我们来具体钩子的代码。

    // XXX Should be moved to embedded !
    /**
     * Shutdown hook which will perform a clean shutdown of Catalina if needed.
     */
    protected class CatalinaShutdownHook extends Thread {
    
        @Override
        public void run() {
            try {
                if (getServer() != null) {
                    Catalina.this.stop();
                }
            } catch (Throwable ex) {
                ExceptionUtils.handleThrowable(ex);
                log.error(sm.getString("catalina.shutdownHookFail"), ex);
            } finally {
                // If JULI is used, shut JULI down *after* the server shuts down
                // so log messages aren't lost
                LogManager logManager = LogManager.getLogManager();
                if (logManager instanceof ClassLoaderLogManager) {
                    ((ClassLoaderLogManager) logManager).shutdown();
                }
            }
        }
    }
    

    可以看到钩子主要的代码是这行

    Catalina.this.stop()
    

    因为CatalinaShutdownHook Catalina类的内部类,所以这句代码就是指向了外部类对象并且调用了stop方法,也就是查看Catalina类的stop方法。

      /**
     * Stop an existing server instance.
     */
    public void stop() {
    
        try {
            // Remove the ShutdownHook first so that server.stop()
            // doesn't get invoked twice
    		//111111111
            if (useShutdownHook) {
                Runtime.getRuntime().removeShutdownHook(shutdownHook);
    
                // If JULI is being used, re-enable JULI's shutdown to ensure
                // log messages are not lost
                LogManager logManager = LogManager.getLogManager();
                if (logManager instanceof ClassLoaderLogManager) {
                    ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                            true);
                }
            }
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            // This will fail on JDK 1.2. Ignoring, as Tomcat can run
            // fine without the shutdown hook.
        }
    
        // Shut down the server
        try {
            Server s = getServer();
            LifecycleState state = s.getState();
            if (LifecycleState.STOPPING_PREP.compareTo(state) <= 0
                    && LifecycleState.DESTROYED.compareTo(state) >= 0) {
                // Nothing to do. stop() was already called
            } else {
    			//22222
                s.stop();
                s.destroy();
            }
        } catch (LifecycleException e) {
            log.error("Catalina.stop", e);
        }
    
    }
    

    其实这段代码就是关闭tomcat的代码,我们来分析下stop方法都做了什么。

    stop方法中1处是移除了钩子,钩子执行的条件又是在异常关闭或者主线程执行完毕,

    • 所以假如tomcat异常关闭,那么调用一次StandardServer.stop()方法(在钩子代码2处调用一次)
    • 如果tomcat是正常关闭的(调用stop.bat/sh)那么会调用两次(第一次是在start方法的数字2处调用,第二次是主线程执行代码完毕以后钩子代码内部数字2处又会调用一次)

    所以在钩子代码一开始的地方先移除钩子,这样可以兼容无论是正常关闭还是异常关闭都只会调用一次StandardServer.stop()

    在钩子代码2的地方先是获取了运行的tomcat的实例server对象,对应实现类StandardServer,然后判断了如果tomcat处于需要关闭的状态则先调用stop方法,再调用destroy方法,我们先来查看StandardServer对象的stop方法。

     @Override
    protected void stopInternal() throws LifecycleException {
    
        setState(LifecycleState.STOPPING);
        fireLifecycleEvent(CONFIGURE_STOP_EVENT, null);
        
        // Stop our defined Services
        for (int i = 0; i < services.length; i++) {
    		//调用StandardService.stop
            services[i].stop();
        }
    
        globalNamingResources.stop();
        
        stopAwait();
    }
    

    StandardServer对象内部没有找到stop方法,但是有stopInternal方法,可以看出依旧使用了模版设计模式,我们简单看下stopInternal方法,类似init,start方法,这里遍历了所有的service调用了stop方法,我们继续查看StandardServicestop方法。

     @Override
    protected void stopInternal() throws LifecycleException {
    
        // Pause connectors first
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                try {
    				//调用connector的pause方法,目的是在关闭的时候拒绝外部的请求
                    connector.pause();
                } catch (Exception e) {
                    log.error(sm.getString(
                            "standardService.connector.pauseFailed",
                            connector), e);
                }
            }
        }
    
        if(log.isInfoEnabled())
            log.info(sm.getString("standardService.stop.name", this.name));
        setState(LifecycleState.STOPPING);
    
        // Stop our defined Container second
        if (container != null) {
            synchronized (container) {
    			//关闭container,container又会调用内部组件的stop方法来依次关闭所有组件
                container.stop();
            }
        }
    
        // Now stop the connectors
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                if (!LifecycleState.STARTED.equals(
                        connector.getState())) {
                    // Connectors only need stopping if they are currently
                    // started. They may have failed to start or may have been
                    // stopped (e.g. via a JMX call)
                    continue;
                }
                try {
    				//停止connector 停止对外的服务
                    connector.stop();
                } catch (Exception e) {
                    log.error(sm.getString(
                            "standardService.connector.stopFailed",
                            connector), e);
                }
            }
        }
    
        synchronized (executors) {
            for (Executor executor: executors) {
                executor.stop();
            }
        }
    }
    

    可以看到代码的形式非常类似之前文章提到的init,start方法,我们只浏览到这部分,下面的读者可以参照之前查看startinit方法的形式自行往下查看源码。到这里StandardServerstop方法我们就看完了,但是在钩子代码里还调用了StandardServerdestroy方法,我们继续查看下StandardServerdestroy方法。

    @Override
    protected void destroyInternal() throws LifecycleException {
        // Destroy our defined Services
        for (int i = 0; i < services.length; i++) {
            services[i].destroy();
        }
    
        globalNamingResources.destroy();
        
        unregister(onameMBeanFactory);
        
        unregister(onameStringCache);
                
        super.destroyInternal();
    }
    

    看到这里大概大家又很熟悉了,形式跟init,start,stop很类似,也是调用了StandardServicedestroy方法,我们就不继续查看了,留给读者自行查看,那么我们钩子方法就看完了。可以看出如果tomcat出现了异常关闭,那么最终是调用的Catalinastop方法,而Catalinastop方法又调用StandardServerstopdestroy方法,我们继续往下看start方法的最后一部分。

    //2222222
    if (await) {
        await();
        stop();
    }
    

    await属性在Bootstrap类的main方法中被设置为true,查看wait方法:

    /**
     * Await and shutdown.
     */
    public void await() {
    
        getServer().await();
    
    }
    

    调用StandardServerawait方法,由于代码很多,省略了部分,只展示了部分重要代码

     /**
     * Wait until a proper shutdown command is received, then return.
     * This keeps the main thread alive - the thread pool listening for http 
     * connections is daemon threads.
     */
    @Override
    public void await() {
        // Negative values - don't wait on port - tomcat is embedded or we just don't like ports
    	//简单的错误检查 省略...
    
        // Set up a server socket to wait on
        try {
            awaitSocket = new ServerSocket(port, 1,InetAddress.getByName(address));
        } catch (IOException e) {
    			//....
        }
    
        try {
            awaitThread = Thread.currentThread();
    
            // Loop waiting for a connection and a valid command
            while (!stopAwait) {
    			//1111
                ServerSocket serverSocket = awaitSocket;
                if (serverSocket == null) {
                    break;
                }
    
                // Wait for the next connection
                Socket socket = null;
                StringBuilder command = new StringBuilder();
                try {
                    InputStream stream;
                    long acceptStartTime = System.currentTimeMillis();
                    try {
                        socket = serverSocket.accept();
                        socket.setSoTimeout(10 * 1000);  // Ten seconds
                        stream = socket.getInputStream();
                    } catch (SocketTimeoutException ste) {
                       //
                   	}
    				//省略部分
                } finally {
                    // Close the socket now that we are done with it
                    try {
                        if (socket != null) {
                            socket.close();
                        }
                    } catch (IOException e) {
                        // Ignore
                    }
                }
    			//22222222222
    				// Read a set of characters from the socket
                    int expected = 1024; // Cut off to avoid DoS attack
                    while (expected < shutdown.length()) {
                        if (random == null)
                            random = new Random();
                        expected += (random.nextInt() % 1024);
                    }
                    while (expected > 0) {
                        int ch = -1;
                        try {
                            ch = stream.read();
                        } catch (IOException e) {
                            log.warn("StandardServer.await: read: ", e);
                            ch = -1;
                        }
                        if (ch < 32)  // Control character or EOF terminates loop
                            break;
                        command.append((char) ch);
                        expected--;
                    }				
    
    
                // Match against our command string
    			//333333333333
                boolean match = command.toString().equals(shutdown);
                if (match) {
                    log.info(sm.getString("standardServer.shutdownViaPort"));
                    break;
                } else
                    log.warn("StandardServer.await: Invalid command '"
                            + command.toString() + "' received");
            }
        } finally {
           
        }
    }
    
     private volatile boolean stopAwait = false;
    

    可以看到await方法中有个while循环,循环中主要做了三件事,在代码1的部分先新建了一个socket,所使用的ip,port分别对应本地和8005,其实这个端口可以更改对应的就是server.xmlServer标签的port,新建了socket以后,调用其accept方法等待,也就是说如果在对应ip,port上有请求进来就会被这里接收到。我们继续看代码2,在第二步拿到accept到的线程的输入流,从输入流中读取拼装字符串,第三步把拼装好的字符串和变量shutdown比对,如果相同就跳出循环。我们查看shutdown指向的是一个SHUTDOWN字符串。看到这里有点不明所以,但是await方法就这样结束了,方法的作用我们暂时不说先继续往下看,查看Catalinastart方法的最后一点stop方法,点开stop方法发现我们在看shutdownhook代码的时候已经看过了,这个方法就是关闭tomcat所有的组件,包括停止和销毁两个步骤。

    看到这里就比较明朗了,在Tomcat启动的末尾,也就是所有组件已经start完毕以后,tomcat内部新建了一个socket用来接收在指定ip指定端口的请求,也就是在server.xml中配置的关闭端口,然后这个socket就会一直等待请求进入(accpet),如果有请求进入了,并且携带的命令是SHUTDOWN(这个也是在server.xml中配置),那么就调用stop方法,也就意味着关闭这个Tomcat,所以Tomcat的关闭就是在指定ip,port上发送一个SHUTDOWN命令即可。

    下面我们来测试下,先在本地启动一个Tomcat,端口8080,shutdown关口8005。

    打开windows cmd命令行

    使用telnet命令 telnet 127.0.0.1 8005

    输入SHUTDOWN然后回车,可以看到tomcat控制台

    那么我们都知道,正常我们关闭Tomcat都是调用bin目录下的shutdown.sh/bat,那么shutdown.bat/sh是做了什么关闭tomcat的呢。
    我们利用在文章http://www.cnblogs.com/coldridgeValley/p/5471421.html中学到的方法,可以在shutdown.bat/sh末尾打印出命令可以发现,其实shutdown.bat/sh调用了catalina.bat/sh并且传递了一个stop参数,而catalina.bat/sh则是调用了Bootstrap.java类的主方法并且传递了参数stop,查看源码可以看到,如果传递的参数是stop,那么就是直接执行Catalina类的stop方法,所以绕来绕去就是无论如何关闭全部是调用Catalinastop方法,所以大家可以仔细多看几遍Catalinastop方法,好了到这里我们Tomcat的启动过程就全部看完了,我们下面继续聊点别的。

  • 相关阅读:
    幸福
    华仔andylau
    计算机常用英语术语、词汇表
    新年新气象
    韶关二日游

    圣诞由来
    哈哈,今天起DK的blog也有隐私了^^
    POJ 2752 Seek the Name, Seek the Fame
    POJ 2406 Power Strings
  • 原文地址:https://www.cnblogs.com/coldridgeValley/p/5631612.html
Copyright © 2020-2023  润新知