创意电子

标题: Tomcat启动脚本startup.bat 源码简析 [打印本页]

作者: 程序那点事    时间: 2021-9-29 00:00
标题: Tomcat启动脚本startup.bat 源码简析
Tomcat是一款我们平时开发过程中最常用到的Servlet容器。本系列博客会记载Tomcat的整体架构、重要组件、IO线程模型、哀求在Tomcat内部的流转过程以及一些Tomcat调优的相干知识。
力图达到以下几个目的:
<hr>Tomcat的启动和停止是通过startup.bat和shutdown.bat这两个脚本实现的。本篇博客就分析下这两个脚本的重要执行流程。
1. startup.bat脚本分析

//关闭命令自身输出@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这个环境变量的取值逻辑如下图所示:

                               
登录/注册后可看大图

如果环境变量设置了CATALINA_HOME,则直接使用环境变量设置的值作为Tomcat安装目录。假如未设置环境变量CATALINA_HOME,则以当前目录作为CATALINA_HOME。此时,如果%CATALINA_HOME%\bin\catalina.bat存在,则批处置惩罚或命令行当前目录作为CATALINA_HOME。假如%CATALINA_HOME%\bin\catalina.bat不存在,则把当前目录的上一级目录作为CATALINA_HOME,然后再判断%CATALINA_HOME%\bin\catalina.bat是否存在。如果存在,则上一级目录就是CATALINA_HOME;否则,提示找不到CATALINA_HOME环境变量并竣事执行。
我们可以看出来,正真执行的脚本是catalina.bat这个脚本,那为什么还要整个startup.bat脚本呢?
其实这个startup.bat脚本就是提供给使用者用来修改的,我们可以在其中设置JAVA_HOME,CATALINA_HOME等环境变量,但我们并不必要深入到较为复杂的catalina.bat脚本中,这正是startup.bat脚本的真正用意所在。
我们知道,软件设计模式中有一个重要的原则就是开闭原则,即我们可以允许别人扩展我们的程序,但在程序发布后,我们拒绝任何修改,因为修改会产生新的Bug,使得我们已经Bug-free的程序又要重新测试。开闭原则是面向对象世界中的一个非常重要的原则,我们可以把这个原则从Java类扩展至源代码级别。startup脚本就是要求用户不要修改catalina.bat脚本,这是符合软件设计思想的。我们如果想要彻底贯彻这个重要的软件设计原则,可以写一个新脚本tomcat.bat,脚本内容大致如下:
set JAVA_HOME=C:\Program Files\Java\jdk1.5.0_09set CATALINA_HOME=C:\carl\it\tomcat_research\jakarta-tomcat-5.0.28   call %CATALINA_HOME%\bin\startup.bat  这个tomcat.bat文件可以存放在任何目录并能执行,并且不必要修改tomcat自带的任何脚本及其它环境变量,这就彻底贯彻了开闭原则。
2. catalina.bat脚本简析

当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&#39;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&#39;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&#39;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 &#39;;&#39; with &#39;&#39; 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 &#39;war:...&#39;) 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的整个执行流程:
拼接命令的代码重要是下面这段:
%_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%通过这段代码我们可以看到Tomcat在启动的时间配置了哪些参数。我们执行下面的命令:
startup.bat arg1 arg2现实执行的命令如下:
"start "Tomcat" "C:\Program Files\Java\jdk1.8.0_73\bin\java.exe" -Djava.util.logging.config.file="D:\software\tomcat-64\apache-tomcat-9.0.0.M21-windows-x64 (1)\apache-tomcat-9.0.0.M21\conf\logging.properties" -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager  "-Djdk.tls.ephemeralDHKeySize=2048" -Djava.protocol.handler.pkgs=org.apache.catalina.webresources   -classpath "D:\software\tomcat-64\apache-tomcat-9.0.0.M21-windows-x64 (1)\apache-tomcat-9.0.0.M21\bin\bootstrap.jar;D:\software\tomcat-64\apache-tomcat-9.0.0.M21-windows-x64 (1)\apache-tomcat-9.0.0.M21\bin\tomcat-juli.jar" -Dcatalina.base="D:\software\tomcat-64\apache-tomcat-9.0.0.M21-windows-x64 (1)\apache-tomcat-9.0.0.M21" -Dcatalina.home="D:\software\tomcat-64\apache-tomcat-9.0.0.M21-windows-x64 (1)\apache-tomcat-9.0.0.M21" -Djava.io.tmpdir="D:\software\tomcat-64\apache-tomcat-9.0.0.M21-windows-x64 (1)\apache-tomcat-9.0.0.M21\temp" org.apache.catalina.startup.Bootstrap arg1 arg2 start"上面命令中开头的start Tomcat的意思是重新开启一个叫tomcat的窗口执行Java命令。
我们看到上面的代码中,有个jpda模式,它是Java平台调试体系布局,可以提供很方便的长途调试,一般情况下我们不会用到这个模式。如果我们想启动这个模式的话可以执行catalina.bat jpda start这个命令。这个模式下我们可以对别的的环境变量JPDA_OPTS进行配置。
3. 关于配置的一些发起

通过上面的脚本,我们可以看到在启动过程中我们可以配置很多环境变量。
通过上面先容我们发现大多数配置都不必要我们自己进行配置,一般情况下我们只必要配置CATALINA_OPTS和JAVA_OPTS这两个配置选项就可以了。这两个参数都可以配置Java运行时所需的一些参数,下面给出两个例子。
在startup脚本中添加配置:
//windowsset JAVA_OPTS=-server -Xms1024m -Xmx2048m -XX:PermSize=256m -XX:MaxPermSize=512m//LinuxJAVA_OPTS="-server -Dfile.encoding=UTF-8 -Xms=512m -Xmx1024m -XX:PermSize=128m -XX:MaxPermSize=256m"4. shutdown.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&#39;t wait on port - tomcat is embedded or we just don&#39;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 &#39;"                            + command.toString() + "&#39; 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:
如果我们将server.xml配置文件修改成:
这样直接在浏览器中输入http://localhost:8005/SHUTDOWN就可以关闭Tomcat了。

作者: 花山137815319    时间: 2021-9-29 13:00
转发了
作者: 花山137815319    时间: 2021-9-29 13:00
转发了
作者: 浪亦无痕    时间: 2021-9-29 14:59
转发了
作者: 浪亦无痕    时间: 2021-9-29 14:59
nice
作者: 小鱼干哪去了    时间: 2021-9-29 18:42
转发了
作者: 51Core    时间: 2021-9-29 21:14
超长
作者: 成熟精灵11    时间: 2021-9-29 23:17
转发了
作者: gg-hjjyfc    时间: 2021-9-30 15:14
转发了
作者: 甜心2318    时间: 2021-9-30 15:35
转发了




欢迎光临 创意电子 (https://wxcydz.cc/) Powered by Discuz! X3.4