本篇主要分析Catalina的start方法内部的两块内容:注册关闭钩子与阻塞监听关闭指令。
注册关闭钩子
// Register shutdown hook
//注册关闭钩子
if (useShutdownHook) {
if (shutdownHook == null) {
// Catalina.this.stop();
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);
}
}
注册新的虚拟机来关闭钩子。
只是一个已初始化但尚未启动的线程。虚拟机开始启用其关闭序列时,它会以某种未指定的顺序启动所有已注册的关闭钩子,
并让它们同时运行。运行完所有的钩子后,如果已启用退出终结,那么虚拟机接着会运行所有未调用的终结方法。
最后,虚拟机会暂停。注意,关闭序列期间会继续运行守护线程,如果通过调用方法来发起关闭序列,
那么也会继续运行非守护线程。
注册关闭钩子,CatalinaShutdownHook,当关闭时,会执行CatalinaShutdownHook中的方法:
@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();
/**
* Stop an existing server instance.
*/
public void stop() {
try {
// Remove the ShutdownHook first so that server.stop()
// doesn't get invoked twice
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 {
s.stop();
s.destroy();
}
} catch (LifecycleException e) {
log.error("Catalina.stop", e);
}
}
移除钩子,调用Server组件的stop、destroy生命周期方法,这个我打算后期专门分析。
阻塞监听关闭指令
// Bootstrap中会设置await为true,其目的在于让tomcat在shutdown端口阻塞监听关闭命令
if (await) {
//等待收到正确的关机命令,然后返回。
await();
//停止现有的服务器实例。
stop();
}
主要看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.
* 这使主线程保持活动状态——侦听http连接的线程池是守护进程线程。
*/
@Override
public void await() {
// Negative values - don't wait on port - tomcat is embedded or we just don't like ports
//负值——不要等待端口——tomcat是嵌入式的,或者我们只是不喜欢端口
if (port == -2) {
// undocumented yet - for embedding apps that are around, alive.
// 还没有正式文件-为嵌入式应用程序周围,活着。
return;
}
if (port == -1) {
try {
awaitThread = Thread.currentThread();
while (!stopAwait) {
try {
Thread.sleep(10000);
} catch (InterruptedException ex) {
// continue and check the flag
}
}
} finally {
awaitThread = null;
}
return;
}
// Set up a server socket to wait on
//设置服务器套接字以等待
try {
awaitSocket = new ServerSocket(port, 1,
InetAddress.getByName(address));
} catch (IOException e) {
log.error("StandardServer.await: create[" + address
+ ":" + port
+ "]: ", e);
return;
}
try {
awaitThread = Thread.currentThread();
// Loop waiting for a connection and a valid command
//循环等待连接和有效命令
while (!stopAwait) {
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) {
// This should never happen but bug 56684 suggests that
// it does.
log.warn(sm.getString("standardServer.accept.timeout",
Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);
continue;
} catch (AccessControlException ace) {
log.warn("StandardServer.accept security exception: "
+ ace.getMessage(), ace);
continue;
} catch (IOException e) {
if (stopAwait) {
// Wait was aborted with socket.close()
break;
}
log.error("StandardServer.await: accept: ", e);
break;
}
// 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;
}
// Control character or EOF (-1) terminates loop
if (ch < 32 || ch == 127) {
break;
}
command.append((char) ch);
expected--;
}
} finally {
// Close the socket now that we are done with it
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
// Ignore
}
}
// Match against our command string
//"SHUTDOWN"
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 {
ServerSocket serverSocket = awaitSocket;
awaitThread = null;
awaitSocket = null;
// Close the server socket and return
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
// Ignore
}
}
}
}
直到接收到"SHUTDOWN"指令,否则一直循环。
至此,简单的分析完了Tomcat的启动过程,这里简单概述一下:
server.xml配置文件解析,组装Catalina实例,注入Server\Service\Connector\Engine\Host\Context等组件。
执行各个组件的生命周期函数init,做初始化操作,通知监听器等。
执行各组件的start方法,最重要是Connector的start
Connector开启服务Socket,创建并启动acceptor, poller线程等。
注册关闭钩子与阻塞监听关闭指令。
之后的文章分析如何处理客户端的请求。