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
方法,我们继续查看StandardService
的stop
方法。
@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
方法,我们只浏览到这部分,下面的读者可以参照之前查看start
,init
方法的形式自行往下查看源码。到这里StandardServer
的stop
方法我们就看完了,但是在钩子代码里还调用了StandardServer
的destroy
方法,我们继续查看下StandardServer
的destroy
方法。
@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
很类似,也是调用了StandardService
的destroy
方法,我们就不继续查看了,留给读者自行查看,那么我们钩子方法就看完了。可以看出如果tomcat出现了异常关闭,那么最终是调用的Catalina
的stop
方法,而Catalina
的stop
方法又调用StandardServer
的stop
和destroy
方法,我们继续往下看start
方法的最后一部分。
//2222222
if (await) {
await();
stop();
}
await
属性在Bootstrap
类的main
方法中被设置为true
,查看wait
方法:
/**
* Await and shutdown.
*/
public void await() {
getServer().await();
}
调用StandardServer
的await
方法,由于代码很多,省略了部分,只展示了部分重要代码
/**
* 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.xml
中Server
标签的port
,新建了socket
以后,调用其accept
方法等待,也就是说如果在对应ip,port上有请求进来就会被这里接收到。我们继续看代码2,在第二步拿到accept
到的线程的输入流,从输入流中读取拼装字符串,第三步把拼装好的字符串和变量shutdown
比对,如果相同就跳出循环。我们查看shutdown
指向的是一个SHUTDOWN
字符串。看到这里有点不明所以,但是await
方法就这样结束了,方法的作用我们暂时不说先继续往下看,查看Catalina
类start
方法的最后一点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
方法,所以绕来绕去就是无论如何关闭全部是调用Catalina
的stop
方法,所以大家可以仔细多看几遍Catalina
的stop
方法,好了到这里我们Tomcat的启动过程就全部看完了,我们下面继续聊点别的。