//关闭命令自身输出@echo off//setlocal命令表示,这边对环境变量的修改只对当前脚本生效setlocal//检查CATALINA_HOME这个环境变量有没设置,如果有设置就使用设置的环境变量//如果没设置,将CATALINA_HOME设置成当前目录。//检测%CATALINA_HOME%\bin\catalina.bat这个脚本存不存在,不存在整合脚本竣事,报错rem Guess CATALINA_HOME if not definedset "CURRENT_DIR=%cd%"if not "%CATALINA_HOME%" == "" goto gotHomeset "CATALINA_HOME=%CURRENT_DIR%"if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHomecd ..set "CATALINA_HOME=%cd%"cd "%CURRENT_DIR%":gotHomeif exist "%CATALINA_HOME%\bin\catalina.bat" goto okHomeecho The CATALINA_HOME environment variable is not defined correctlyecho This environment variable is needed to run this programgoto end:okHome//预备启动脚本set "EXECUTABLE=%CATALINA_HOME%\bin\catalina.bat"rem Check that target executable existsif exist "%EXECUTABLE%" goto okExececho Cannot find "%EXECUTABLE%"echo This file is needed to run this programgoto end:okExec//拼接catalina.bat这个脚本的命令行参数rem Get remaining unshifted command line arguments and save them in theset CMD_LINE_ARGS=:setArgsif ""%1""=="""" goto doneSetArgsset CMD_LINE_ARGS=%CMD_LINE_ARGS% %1shiftgoto setArgs:doneSetArgs//执行catalina.bat这个脚本,执行start,并添加命令行参数call "%EXECUTABLE%" start %CMD_LINE_ARGS%:end整个startup.bat脚本很简朴,根据CATALINA_HOME检测catalina.bat是否存在,不存在的话就报错,存在的话拼接命令行参数然后执行catalina.bat这个脚本。CATALINA_HOME这个环境变量的取值逻辑如下图所示:
当startup脚本完成环境变量的设置后,就开始调用catalina.bat脚本来启动Tomcat。Catalina脚本的重要任务是根据环境变量和不同的命令行参数,拼凑出完备的java命令,调用Tomcat的主启动类org.apache.catalina.startup.Bootstrap来启动Tomcat。
@echo offrem ---------------------------------------------------------------------------rem Start/Stop Script for the CATALINA Serverremrem Environment Variable Prerequisitesremrem Do not set the variables in this script. Instead put them into a scriptrem setenv.bat in CATALINA_BASE/bin to keep your customizations separate.remrem WHEN RUNNING TOMCAT AS A WINDOWS SERVICE:rem Note that the environment variables that affect the behavior of thisrem script will have no effect at all on Windows Services. As such, anyrem local customizations made in a CATALINA_BASE/bin/setenv.bat scriptrem will also have no effect on Tomcat when launched as a Windows Service.rem The configuration that controls Windows Services is stored in the Windowsrem Registry, and is most conveniently maintained using the "tomcatXw.exe"rem maintenance utility, where "X" is the major version of Tomcat you arerem running.remrem CATALINA_HOME May point at your Catalina "build" directory.remrem CATALINA_BASE (Optional) Base directory for resolving dynamic portionsrem of a Catalina installation. If not present, resolves torem the same directory that CATALINA_HOME points to.remrem CATALINA_OPTS (Optional) Java runtime options used when the "start",rem "run" or "debug" command is executed.rem Include here and not in JAVA_OPTS all options, that shouldrem only be used by Tomcat itself, not by the stop process,rem the version command etc.rem Examples are heap size, GC logging, JMX ports etc.remrem CATALINA_TMPDIR (Optional) Directory path location of temporary directoryrem the JVM should use (java.io.tmpdir). Defaults torem %CATALINA_BASE%\temp.remrem JAVA_HOME Must point at your Java Development Kit installation.rem Required to run the with the "debug" argument.remrem JRE_HOME Must point at your Java Runtime installation.rem Defaults to JAVA_HOME if empty. If JRE_HOME and JAVA_HOMErem are both set, JRE_HOME is used.remrem JAVA_OPTS (Optional) Java runtime options used when any commandrem is executed.rem Include here and not in CATALINA_OPTS all options, thatrem should be used by Tomcat and also by the stop process,rem the version command etc.rem Most options should go into CATALINA_OPTS.remrem JPDA_TRANSPORT (Optional) JPDA transport used when the "jpda start"rem command is executed. The default is "dt_socket".remrem JPDA_ADDRESS (Optional) Java runtime options used when the "jpda start"rem command is executed. The default is localhost:8000.remrem JPDA_SUSPEND (Optional) Java runtime options used when the "jpda start"rem command is executed. Specifies whether JVM should suspendrem execution immediately after startup. Default is "n".remrem JPDA_OPTS (Optional) Java runtime options used when the "jpda start"rem command is executed. If used, JPDA_TRANSPORT, JPDA_ADDRESS,rem and JPDA_SUSPEND are ignored. Thus, all required jpdarem options MUST be specified. The default is:remrem -agentlib:jdwp=transport=%JPDA_TRANSPORT%,rem address=%JPDA_ADDRESS%,server=y,suspend=%JPDA_SUSPEND%remrem JSSE_OPTS (Optional) Java runtime options used to control the TLSrem implementation when JSSE is used. Default is:rem "-Djdk.tls.ephemeralDHKeySize=2048"remrem LOGGING_CONFIG (Optional) Override Tomcat's logging config filerem Example (all one line)rem set LOGGING_CONFIG="-Djava.util.logging.config.file=%CATALINA_BASE%\conf\logging.properties"remrem LOGGING_MANAGER (Optional) Override Tomcat's logging managerrem Example (all one line)rem set LOGGING_MANAGER="-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager"remrem TITLE (Optional) Specify the title of Tomcat window. The defaultrem TITLE is Tomcat if it's not specified.rem Example (all one line)rem set TITLE=Tomcat.Cluster#1.Server#1 [%DATE% %TIME%]rem ---------------------------------------------------------------------------setlocalrem Suppress Terminate batch job on CTRL+Cif not ""%1"" == ""run"" goto mainEntryif "%TEMP%" == "" goto mainEntryif exist "%TEMP%\%~nx0.run" goto mainEntryecho Y>"%TEMP%\%~nx0.run"if not exist "%TEMP%\%~nx0.run" goto mainEntryecho Y>"%TEMP%\%~nx0.Y"call "%~f0" %* NUL 2>&1exit /B %RETVAL%:mainEntrydel /Q "%TEMP%\%~nx0.run" >NUL 2>&1//防止用户直接执行catalina.bat这个脚本,再次检测下CATALINA_HOME环境变量//检测catalina.bat这个脚本存不存在rem Guess CATALINA_HOME if not definedset "CURRENT_DIR=%cd%"if not "%CATALINA_HOME%" == "" goto gotHomeset "CATALINA_HOME=%CURRENT_DIR%"if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHomecd ..set "CATALINA_HOME=%cd%"cd "%CURRENT_DIR%":gotHomeif exist "%CATALINA_HOME%\bin\catalina.bat" goto okHomeecho The CATALINA_HOME environment variable is not defined correctlyecho This environment variable is needed to run this programgoto end:okHome//检测CATALINA_BASE是否存在,不存在就设置成和CATALINA_HOME一致rem Copy CATALINA_BASE from CATALINA_HOME if not definedif not "%CATALINA_BASE%" == "" goto gotBaseset "CATALINA_BASE=%CATALINA_HOME%":gotBaserem Ensure that neither CATALINA_HOME nor CATALINA_BASE contains a semi-colonrem as this is used as the separator in the classpath and Java provides norem mechanism for escaping if the same character appears in the path. Check thisrem by replacing all occurrences of ';' with '' and checking that neitherrem CATALINA_HOME nor CATALINA_BASE have changedif "%CATALINA_HOME%" == "%CATALINA_HOME:;=%" goto homeNoSemicolonecho Using CATALINA_HOME: "%CATALINA_HOME%"echo Unable to start as CATALINA_HOME contains a semicolon (;) charactergoto end:homeNoSemicolonif "%CATALINA_BASE%" == "%CATALINA_BASE:;=%" goto baseNoSemicolonecho Using CATALINA_BASE: "%CATALINA_BASE%"echo Unable to start as CATALINA_BASE contains a semicolon (;) charactergoto end:baseNoSemicolonrem Ensure that any user defined CLASSPATH variables are not used on startup,rem but allow them to be specified in setenv.bat, in rare case when it is needed.set CLASSPATH=//如果%CATALINA_BASE%\bin\setenv.bat存在,执行setenv.bat这个脚本,不存在//执行%CATALINA_HOME%\bin\setenv.bat这个脚本来设置环境变量rem Get standard environment variablesif not exist "%CATALINA_BASE%\bin\setenv.bat" goto checkSetenvHomecall "%CATALINA_BASE%\bin\setenv.bat"goto setenvDone:checkSetenvHomeif exist "%CATALINA_HOME%\bin\setenv.bat" call "%CATALINA_HOME%\bin\setenv.bat":setenvDone//如果%CATALINA_HOME%\bin\setclasspath.bat存在,执行setclasspath.bat,这个脚本的重要作用是检测//JAVA_HOME有没有精确设置rem Get standard Java environment variablesif exist "%CATALINA_HOME%\bin\setclasspath.bat" goto okSetclasspathecho Cannot find "%CATALINA_HOME%\bin\setclasspath.bat"echo This file is needed to run this programgoto end:okSetclasspathcall "%CATALINA_HOME%\bin\setclasspath.bat" %1if errorlevel 1 goto end//将bootstrap.jar加入classpathrem Add on extra jar file to CLASSPATHrem Note that there are no quotes as we do not want to introduce randomrem quotes into the CLASSPATHif "%CLASSPATH%" == "" goto emptyClasspathset "CLASSPATH=%CLASSPATH%;":emptyClasspathset "CLASSPATH=%CLASSPATH%%CATALINA_HOME%\bin\bootstrap.jar"//设置CATALINA_TMPDIR=%CATALINA_BASE%\tempif not "%CATALINA_TMPDIR%" == "" goto gotTmpdirset "CATALINA_TMPDIR=%CATALINA_BASE%\temp":gotTmpdir//将tomcat-juli.jar加入classpathrem Add tomcat-juli.jar to classpathrem tomcat-juli.jar can be over-ridden per instanceif not exist "%CATALINA_BASE%\bin\tomcat-juli.jar" goto juliClasspathHomeset "CLASSPATH=%CLASSPATH%;%CATALINA_BASE%\bin\tomcat-juli.jar"goto juliClasspathDone:juliClasspathHomeset "CLASSPATH=%CLASSPATH%;%CATALINA_HOME%\bin\tomcat-juli.jar":juliClasspathDone//如果没有设置JSSE_OPTS,JSSE_OPTS="-Djdk.tls.ephemeralDHKeySize=2048"//再将JAVA_OPTS设置成"JAVA_OPTS=%JAVA_OPTS% %JSSE_OPTS%"if not "%JSSE_OPTS%" == "" goto gotJsseOptsset JSSE_OPTS="-Djdk.tls.ephemeralDHKeySize=2048":gotJsseOptsset "JAVA_OPTS=%JAVA_OPTS% %JSSE_OPTS%"rem Register custom URL handlersrem Do this here so custom URL handles (specifically 'war:...') can be used in the security policyset "JAVA_OPTS=%JAVA_OPTS% -Djava.protocol.handler.pkgs=org.apache.catalina.webresources"//将日记配置文件设置成logging.propertiesif not "%LOGGING_CONFIG%" == "" goto noJuliConfigset LOGGING_CONFIG=-Dnopif not exist "%CATALINA_BASE%\conf\logging.properties" goto noJuliConfigset LOGGING_CONFIG=-Djava.util.logging.config.file="%CATALINA_BASE%\conf\logging.properties":noJuliConfig//配置默认的LOGGING_MANAGERif not "%LOGGING_MANAGER%" == "" goto noJuliManagerset LOGGING_MANAGER=-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager:noJuliManagerrem ----- Execute The Requested Command ---------------------------------------echo Using CATALINA_BASE: "%CATALINA_BASE%"echo Using CATALINA_HOME: "%CATALINA_HOME%"echo Using CATALINA_TMPDIR: "%CATALINA_TMPDIR%"if ""%1"" == ""debug"" goto use_jdkecho Using JRE_HOME: "%JRE_HOME%"goto java_dir_displayed:use_jdkecho Using JAVA_HOME: "%JAVA_HOME%":java_dir_displayedecho Using CLASSPATH: "%CLASSPATH%"set _EXECJAVA=%_RUNJAVA%set MAINCLASS=org.apache.catalina.startup.Bootstrapset ACTION=startset SECURITY_POLICY_FILE=set DEBUG_OPTS=set JPDA=if not ""%1"" == ""jpda"" goto noJpdaset JPDA=jpdaif not "%JPDA_TRANSPORT%" == "" goto gotJpdaTransportset JPDA_TRANSPORT=dt_socket:gotJpdaTransportif not "%JPDA_ADDRESS%" == "" goto gotJpdaAddressset JPDA_ADDRESS=localhost:8000:gotJpdaAddressif not "%JPDA_SUSPEND%" == "" goto gotJpdaSuspendset JPDA_SUSPEND=n:gotJpdaSuspendif not "%JPDA_OPTS%" == "" goto gotJpdaOptsset JPDA_OPTS=-agentlib:jdwp=transport=%JPDA_TRANSPORT%,address=%JPDA_ADDRESS%,server=y,suspend=%JPDA_SUSPEND%:gotJpdaOptsshift:noJpdaif ""%1"" == ""debug"" goto doDebugif ""%1"" == ""run"" goto doRunif ""%1"" == ""start"" goto doStartif ""%1"" == ""stop"" goto doStopif ""%1"" == ""configtest"" goto doConfigTestif ""%1"" == ""version"" goto doVersionecho Usage: catalina ( commands ... )echo commands:echo debug Start Catalina in a debuggerecho debug -security Debug Catalina with a security managerecho jpda start Start Catalina under JPDA debuggerecho run Start Catalina in the current windowecho run -security Start in the current window with security managerecho start Start Catalina in a separate windowecho start -security Start in a separate window with security managerecho stop Stop Catalinaecho configtest Run a basic syntax check on server.xmlecho version What version of tomcat are you running?goto end:doDebugshiftset _EXECJAVA=%_RUNJDB%set DEBUG_OPTS=-sourcepath "%CATALINA_HOME%\..\..\java"if not ""%1"" == ""-security"" goto execCmdshiftecho Using Security Managerset "SECURITY_POLICY_FILE=%CATALINA_BASE%\conf\catalina.policy"goto execCmd:doRunshiftif not ""%1"" == ""-security"" goto execCmdshiftecho Using Security Managerset "SECURITY_POLICY_FILE=%CATALINA_BASE%\conf\catalina.policy"goto execCmd:doStartshiftif "%TITLE%" == "" set TITLE=Tomcatset _EXECJAVA=start "%TITLE%" %_RUNJAVA%if not ""%1"" == ""-security"" goto execCmdshiftecho Using Security Managerset "SECURITY_POLICY_FILE=%CATALINA_BASE%\conf\catalina.policy"goto execCmd:doStopshiftset ACTION=stopset CATALINA_OPTS=goto execCmd:doConfigTestshiftset ACTION=configtestset CATALINA_OPTS=goto execCmd:doVersion%_EXECJAVA% -classpath "%CATALINA_HOME%\lib\catalina.jar" org.apache.catalina.util.ServerInfogoto end:execCmdrem Get remaining unshifted command line arguments and save them in theset CMD_LINE_ARGS=:setArgsif ""%1""=="""" goto doneSetArgsset CMD_LINE_ARGS=%CMD_LINE_ARGS% %1shiftgoto setArgs:doneSetArgsrem Execute Java with the applicable propertiesif not "%JPDA%" == "" goto doJpdaif not "%SECURITY_POLICY_FILE%" == "" goto doSecurity//一般情况下都会执行到这个脚本语句%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%goto end:doSecurity%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -classpath "%CLASSPATH%" -Djava.security.manager -Djava.security.policy=="%SECURITY_POLICY_FILE%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%goto end:doJpdaif not "%SECURITY_POLICY_FILE%" == "" goto doSecurityJpda%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %JPDA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -classpath "%CLASSPATH%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%goto end:doSecurityJpda%_EXECJAVA% %LOGGING_CONFIG% %LOGGING_MANAGER% %JAVA_OPTS% %JPDA_OPTS% %CATALINA_OPTS% %DEBUG_OPTS% -classpath "%CLASSPATH%" -Djava.security.manager -Djava.security.policy=="%SECURITY_POLICY_FILE%" -Dcatalina.base="%CATALINA_BASE%" -Dcatalina.home="%CATALINA_HOME%" -Djava.io.tmpdir="%CATALINA_TMPDIR%" %MAINCLASS% %CMD_LINE_ARGS% %ACTION%goto end:end总结下catalina.bat的整个执行流程:
setlocalrem Guess CATALINA_HOME if not definedset "CURRENT_DIR=%cd%"if not "%CATALINA_HOME%" == "" goto gotHomeset "CATALINA_HOME=%CURRENT_DIR%"if exist "%CATALINA_HOME%\bin\catalina.bat" goto okHomecd ..set "CATALINA_HOME=%cd%"cd "%CURRENT_DIR%":gotHomeif exist "%CATALINA_HOME%\bin\catalina.bat" goto okHomeecho The CATALINA_HOME environment variable is not defined correctlyecho This environment variable is needed to run this programgoto end:okHomeset "EXECUTABLE=%CATALINA_HOME%\bin\catalina.bat"rem Check that target executable existsif exist "%EXECUTABLE%" goto okExececho Cannot find "%EXECUTABLE%"echo This file is needed to run this programgoto end:okExecrem Get remaining unshifted command line arguments and save them in theset CMD_LINE_ARGS=:setArgsif ""%1""=="""" goto doneSetArgsset CMD_LINE_ARGS=%CMD_LINE_ARGS% %1shiftgoto setArgs:doneSetArgs//这边是关键call "%EXECUTABLE%" stop %CMD_LINE_ARGS%:end我们可以发现shutdown.bat的逻辑和startup.bat的逻辑是一样的,也是先设置CATALINA_HOME,拼接命令行参数,不一样是最后执行的是catalina.bat stop。
这边照旧有必要来分析下Tomcat的关闭原理的。我们知道,Tomcat中的工作线程都是demo线程,如果没有一个主线程的话那么Tomcat会立即停止运行的。(前台线程死亡后,demo线程会主动消失。)那么Tomcat是在哪里启动的这个主线程的呢?通过代码跟踪,我们发现是StandardServer这个类的await方法创建了这个主线程,这个线程hold了Tomcat程序不绝止。
public void await() { // Negative values - don't wait on port - tomcat is embedded or we just don't like ports if (getPortWithOffset() == -2) { // undocumented yet - for embedding apps that are around, alive. return; } // if (getPortWithOffset() == -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(getPortWithOffset(), 1, InetAddress.getByName(address)); } catch (IOException e) { log.error(sm.getString("standardServer.awaitSocket.fail", address, String.valueOf(getPortWithOffset()), String.valueOf(getPort()), String.valueOf(getPortOffset())), 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 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 } } } }StandardServer默认监听的端口是8005端口(注意这个端口和Connector组件监听端口的区别),当发现监听到的连接的输入流中的内容与默认配置的值匹配(该值默认为字符串SHUTDOWN)则跳出循环。否则该方法会一直循环执行下去。所以只要没人向8005端口发送shutdown,这个线程就会一直运行,其他的保卫线程也就不会消失。
固然我们可以将StandardServer的监听端口设置成-1(SpringBoot中内嵌的Tomcat就是这么做的),此时Tomcat不会再监听详细的端口,主线程会每10秒睡眠一次,知道我们手动将stopAwait设置为true。
知道了这个原理,我们只要将这个主线程竣事掉,整个Tomcat程序就竣事了。通过上面的分析,可以有两种方式来关闭Tomcat: