熟悉Tomcat的工程師們,肯定都知道Tomcat是如何啟動與停止的。對于startup.sh、startup.bat、shutdown.sh、shutdown.bat等腳本或者批處理命令,大家一定知道改如何使用它,但是它們究竟是如何實(shí)現(xiàn)的,尤其是shutdown.sh腳本(或者shutdown.bat)究竟是如何和Tomcat進(jìn)程通信的呢?本文將通過對Tomcat7.0的源碼閱讀,深入剖析這一過程。
由于在生產(chǎn)環(huán)境中,Tomcat一般部署在linux系統(tǒng)下,所以本文將以startup.sh和shutdown.sh等shell腳本為準(zhǔn),對Tomcat的啟動與停止進(jìn)行分析。
我們啟動Tomcat的命令如下:
sh startup.sh
所以,將從shell腳本startup.sh開始分析Tomcat的啟動過程。startup.sh的腳本代碼見代碼清單1。
代碼清單1
os400=falsecase "`uname`" inOS400*) os400=true;;esac# resolve links - $0 may be a softlinkPRG="$0"while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> /(.*/)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`/"$link" fidonePRGDIR=`dirname "$PRG"`EXECUTABLE=catalina.sh# Check that target executable existsif $os400; then # -x will Only work on the os400 if the files are: # 1. owned by the user # 2. owned by the PRIMARY group of the user # this will not work if the user belongs in secondary groups evalelse if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then echo "Cannot find $PRGDIR/$EXECUTABLE" echo "The file is absent or does not have execute permission" echo "This file is needed to run this program" exit 1 fifiexec "$PRGDIR"/"$EXECUTABLE" start "$@"
代碼清單1中有兩個主要的變量,分別是:
根據(jù)最后一行代碼:exec "$PRGDIR"/"$EXECUTABLE" start "$@",我們知道執(zhí)行了shell腳本catalina.sh,并且傳遞參數(shù)start。catalina.sh中接收到start參數(shù)后的執(zhí)行的腳本分支見代碼清單2。
代碼清單2
elif [ "$1" = "start" ] ; then# 此處省略參數(shù)校驗(yàn)的腳本 shift touch "$CATALINA_OUT" if [ "$1" = "-security" ] ; then if [ $have_tty -eq 1 ]; then echo "Using Security Manager" fi shift eval "/"$_RUNjava/"" "/"$LOGGING_CONFIG/"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS / -Djava.endorsed.dirs="/"$JAVA_ENDORSED_DIRS/"" -classpath "/"$CLASSPATH/"" / -Djava.security.manager / -Djava.security.policy=="/"$CATALINA_BASE/conf/catalina.policy/"" / -Dcatalina.base="/"$CATALINA_BASE/"" / -Dcatalina.home="/"$CATALINA_HOME/"" / -Djava.io.tmpdir="/"$CATALINA_TMPDIR/"" / org.apache.catalina.startup.Bootstrap "$@" start / >> "$CATALINA_OUT" 2>&1 "&" else eval "/"$_RUNJAVA/"" "/"$LOGGING_CONFIG/"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS / -Djava.endorsed.dirs="/"$JAVA_ENDORSED_DIRS/"" -classpath "/"$CLASSPATH/"" / -Dcatalina.base="/"$CATALINA_BASE/"" / -Dcatalina.home="/"$CATALINA_HOME/"" / -Djava.io.tmpdir="/"$CATALINA_TMPDIR/"" / org.apache.catalina.startup.Bootstrap "$@" start / >> "$CATALINA_OUT" 2>&1 "&" fi if [ ! -z "$CATALINA_PID" ]; then echo $! > "$CATALINA_PID" fi echo "Tomcat started."
從代碼清單2可以看出,最終使用java命令執(zhí)行了org.apache.catalina.startup.Bootstrap類中的main方法,參數(shù)也是start。Bootstrap的main方法的實(shí)現(xiàn)見代碼清單3。
代碼清單3
/** * Main method, used for testing only. * * @param args Command line arguments to be processed */ public static void main(String args[]) { if (daemon == null) { // Don't set daemon until init() has completed Bootstrap bootstrap = new Bootstrap(); try { bootstrap.init(); } catch (Throwable t) { t.printStackTrace(); return; } daemon = bootstrap; } try { String command = "start"; if (args.length > 0) { command = args[args.length - 1]; } if (command.equals("startd")) { args[args.length - 1] = "start"; daemon.load(args); daemon.start(); } else if (command.equals("stopd")) { args[args.length - 1] = "stop"; daemon.stop(); } else if (command.equals("start")) { daemon.setAwait(true); daemon.load(args); daemon.start(); } else if (command.equals("stop")) { daemon.stopServer(args); } else { log.warn("Bootstrap: command /"" + command + "/" does not exist."); } } catch (Throwable t) { t.printStackTrace(); } }
從代碼清單3可以看出,當(dāng)傳遞參數(shù)start的時候,command等于start,此時main方法的執(zhí)行步驟如下:
步驟一 初始化Bootstrap
Bootstrap的init方法(見代碼清單4)的執(zhí)行步驟如下:
代碼清單4
/** * Initialize daemon. */ public void init() throws Exception { // Set Catalina path setCatalinaHome(); setCatalinaBase(); initClassLoaders(); Thread.currentThread().setContextClassLoader(catalinaLoader); SecurityClassLoad.securityClassLoad(catalinaLoader); // Load our startup class and call its process() method if (log.isDebugEnabled()) log.debug("Loading startup class"); Class<?> startupClass = catalinaLoader.loadClass ("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.newInstance(); // Set the shared extensions class loader if (log.isDebugEnabled()) log.debug("Setting startup class properties"); String methodName = "setParentClassLoader"; Class<?> paramTypes[] = new Class[1]; paramTypes[0] = Class.forName("java.lang.ClassLoader"); Object paramValues[] = new Object[1]; paramValues[0] = sharedLoader; Method method = startupInstance.getClass().getMethod(methodName, paramTypes); method.invoke(startupInstance, paramValues); catalinaDaemon = startupInstance; }
步驟二 加載、解析server.xml配置文件
當(dāng)傳遞參數(shù)start的時候,會調(diào)用Bootstrap的load方法(見代碼清單5),其作用是用反射調(diào)用catalinaDaemon(類型是Catalina)的load方法加載和解析server.xml配置文件,具體細(xì)節(jié)已在《TOMCAT源碼分析——SERVER.XML文件的加載與解析》一文中詳細(xì)介紹,有興趣的朋友可以選擇閱讀。
代碼清單5
/** * Load daemon. */ private void load(String[] arguments) throws Exception { // Call the load() method String methodName = "load"; Object param[]; Class<?> paramTypes[]; if (arguments==null || arguments.length==0) { paramTypes = null; param = null; } else { paramTypes = new Class[1]; paramTypes[0] = arguments.getClass(); param = new Object[1]; param[0] = arguments; } Method method = catalinaDaemon.getClass().getMethod(methodName, paramTypes); if (log.isDebugEnabled()) log.debug("Calling startup class " + method); method.invoke(catalinaDaemon, param); }
步驟三 啟動Tomcat
當(dāng)傳遞參數(shù)start的時候,調(diào)用Bootstrap的load方法之后會接著調(diào)用start方法(見代碼清單6)啟動Tomcat,此方法實(shí)際是用反射調(diào)用了catalinaDaemon(類型是Catalina)的start方法。
代碼清單6
/** * Start the Catalina daemon. */ public void start() throws Exception { if( catalinaDaemon==null ) init(); Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null); method.invoke(catalinaDaemon, (Object [])null); }
Catalina的start方法(見代碼清單7)的執(zhí)行步驟如下:
代碼清單7
/** * 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.error("Catalina.start: ", e); } long t2 = System.nanoTime(); if(log.isInfoEnabled()) log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms"); try { // Register shutdown hook 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); } } } catch (Throwable t) { // This will fail on JDK 1.2. Ignoring, as Tomcat can run // fine without the shutdown hook. } if (await) { await(); stop(); } }
Catalina的await方法(見代碼清單8)實(shí)際只是代理執(zhí)行了Server容器的await方法。
代碼清單8
/** * Await and shutdown. */ public void await() { getServer().await(); }
以Server的默認(rèn)實(shí)現(xiàn)StandardServer為例,其await方法(見代碼清單9)的執(zhí)行步驟如下:
代碼清單9
public void await() { // Negative values - don't wait on port - tomcat is embedded or we just don't like ports gja if( port == -2 ) { // undocumented yet - for embedding apps that are around, alive. return; } if( port==-1 ) { while( true ) { try { Thread.sleep( 10000 ); } catch( InterruptedException ex ) { } if( stopAwait ) return; } } // Set up a server socket to wait on ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(port, 1, InetAddress.getByName(address)); } catch (IOException e) { log.error("StandardServer.await: create[" + address + ":" + port + "]: ", e); System.exit(1); } // Loop waiting for a connection and a valid command while (true) { // Wait for the next connection Socket socket = null; InputStream stream = null; try { socket = serverSocket.accept(); socket.setSoTimeout(10 * 1000); // Ten seconds stream = socket.getInputStream(); } catch (accessControlException ace) { log.warn("StandardServer.accept security exception: " + ace.getMessage(), ace); continue; } catch (IOException e) { log.error("StandardServer.await: accept: ", e); System.exit(1); } // Read a set of characters from the socket StringBuilder command = new StringBuilder(); 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--; } // Close the socket now that we are done with it try { 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"); } // Close the server socket and return try { serverSocket.close(); } catch (IOException e) { // Ignore } }
至此,Tomcat啟動完畢。很多人可能會問,執(zhí)行sh shutdown.sh腳本時,是如何與Tomcat進(jìn)程通信的呢?如果要與Tomcat的ServerSocket通信,socket客戶端如何知道服務(wù)端的連接地址與端口呢?下面會慢慢說明。
我們停止Tomcat的命令如下:
sh shutdown.sh
所以,將從shell腳本shutdown.sh開始分析Tomcat的停止過程。shutdown.sh的腳本代碼見代碼清單10。
代碼清單10
os400=falsecase "`uname`" inOS400*) os400=true;;esac# resolve links - $0 may be a softlinkPRG="$0"while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> /(.*/)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`/"$link" fidonePRGDIR=`dirname "$PRG"`EXECUTABLE=catalina.sh# Check that target executable existsif $os400; then # -x will Only work on the os400 if the files are: # 1. owned by the user # 2. owned by the PRIMARY group of the user # this will not work if the user belongs in secondary groups evalelse if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then echo "Cannot find $PRGDIR/$EXECUTABLE" echo "The file is absent or does not have execute permission" echo "This file is needed to run this program" exit 1 fifiexec "$PRGDIR"/"$EXECUTABLE" stop "$@"
代碼清單10和代碼清單1非常相似,其中也有兩個主要的變量,分別是:
根據(jù)最后一行代碼:exec "$PRGDIR"/"$EXECUTABLE" stop "$@",我們知道執(zhí)行了shell腳本catalina.sh,并且傳遞參數(shù)stop。catalina.sh中接收到stop參數(shù)后的執(zhí)行的腳本分支見代碼清單11。
代碼清單11
elif [ "$1" = "stop" ] ; then #省略參數(shù)校驗(yàn)?zāi)_本 eval "/"$_RUNJAVA/"" $LOGGING_MANAGER $JAVA_OPTS / -Djava.endorsed.dirs="/"$JAVA_ENDORSED_DIRS/"" -classpath "/"$CLASSPATH/"" / -Dcatalina.base="/"$CATALINA_BASE/"" / -Dcatalina.home="/"$CATALINA_HOME/"" / -Djava.io.tmpdir="/"$CATALINA_TMPDIR/"" / org.apache.catalina.startup.Bootstrap "$@" stop
從代碼清單11可以看出,最終使用java命令執(zhí)行了org.apache.catalina.startup.Bootstrap類中的main方法,參數(shù)是stop。從代碼清單3可以看出,當(dāng)傳遞參數(shù)stop的時候,command等于stop,此時main方法的執(zhí)行步驟如下:
步驟一 初始化Bootstrap
已經(jīng)在啟動過程分析中介紹, 不再贅述。
步驟二 停止服務(wù)
通過調(diào)用Bootstrap的stopServer方法(見代碼清單12)停止Tomcat,其實(shí)質(zhì)是用反射調(diào)用catalinaDaemon(類型是Catalina)的stopServer方法。
代碼清單12
/** * Stop the standalone server. */ public void stopServer(String[] arguments) throws Exception { Object param[]; Class<?> paramTypes[]; if (arguments==null || arguments.length==0) { paramTypes = null; param = null; } else { paramTypes = new Class[1]; paramTypes[0] = arguments.getClass(); param = new Object[1]; param[0] = arguments; } Method method = catalinaDaemon.getClass().getMethod("stopServer", paramTypes); method.invoke(catalinaDaemon, param); }
Catalina的stopServer方法(見代碼清單13)的執(zhí)行步驟如下:
代碼清單13
public void stopServer() { stopServer(null); } public void stopServer(String[] arguments) { if (arguments != null) { arguments(arguments); } if( getServer() == null ) { // Create and execute our Digester Digester digester = createStopDigester(); digester.setClassLoader(Thread.currentThread().getContextClassLoader()); File file = configFile(); try { InputSource is = new InputSource("file://" + file.getAbsolutePath()); FileInputStream fis = new FileInputStream(file); is.setByteStream(fis); digester.push(this); digester.parse(is); fis.close(); } catch (Exception e) { log.error("Catalina.stop: ", e); System.exit(1); } } // Stop the existing server try { if (getServer().getPort()>0) { Socket socket = new Socket(getServer().getAddress(), getServer().getPort()); OutputStream stream = socket.getOutputStream(); String shutdown = getServer().getShutdown(); for (int i = 0; i < shutdown.length(); i++) stream.write(shutdown.charAt(i)); stream.flush(); stream.close(); socket.close(); } else { log.error(sm.getString("catalina.stopServer")); System.exit(1); } } catch (IOException e) { log.error("Catalina.stop: ", e); System.exit(1); } }
最后,我們看看Catalina的stop方法(見代碼清單14)的實(shí)現(xiàn),其執(zhí)行步驟如下:
代碼清單14
/** * 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 jiaan LogManager logManager = LogManager.getLogManager(); if (logManager instanceof ClassLoaderLogManager) { ((ClassLoaderLogManager) logManager).setUseShutdownHook( true); } } } catch (Throwable t) { // This will fail on JDK 1.2. Ignoring, as Tomcat can run // fine without the shutdown hook. } // Shut down the server try { getServer().stop(); } catch (LifecycleException e) { log.error("Catalina.stop", e); } }
通過對Tomcat源碼的分析我們了解到Tomcat的啟動和停止都離不開org.apache.catalina.startup.Bootstrap。當(dāng)停止Tomcat時,已經(jīng)啟動的Tomcat作為socket服務(wù)端,停止腳本啟動的Bootstrap進(jìn)程作為socket客戶端向服務(wù)端發(fā)送shutdown命令,兩個進(jìn)程通過共享server.xml里Server標(biāo)簽的端口以及地址信息打通了socket的通信。
如需轉(zhuǎn)載,請標(biāo)明本文作者及出處——作者:jiaan.gja,本文原創(chuàng)首發(fā):博客園,原文鏈接:http://www.companysz.com/jiaan-geng/p/4872550.html
|
新聞熱點(diǎn)
疑難解答
圖片精選