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的启动过程就全部看完了,我们下面继续聊点别的。

原文地址:https://www.cnblogs.com/coldridgeValley/p/5631612.html