【Web容器】Tomcat源码分析(4)-启动与停止服务

来源:互联网 发布:java微信 api好友列表 编辑:程序博客网 时间:2024/05/21 19:24

前言

  熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的。对于startup.sh、startup.bat、shutdown.sh、shutdown.bat等脚本或者批处理命令,大家一定知道改如何使用它,但是它们究竟是如何实现的,尤其是shutdown.sh脚本(或者shutdown.bat)究竟是如何和Tomcat进程通信的呢?本文将通过对Tomcat7.0的源码阅读,深入剖析这一过程。

  由于在生产环境中,Tomcat一般部署在Linux系统下,所以本文将以startup.sh和shutdown.sh等shell脚本为准,对Tomcat的启动与停止进行分析。

启动过程分析

  我们启动Tomcat的命令如下:

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. sh startup.sh  

所以,将从shell脚本startup.sh开始分析Tomcat的启动过程。startup.sh的脚本代码见代码清单1。

代码清单1

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. os400=false  
  2. case "`uname`" in  
  3. OS400*) os400=true;;  
  4. esac  
  5.   
  6. # resolve links - $0 may be a softlink  
  7. PRG="$0"  
  8.   
  9. while [ -h "$PRG" ] ; do  
  10.   ls=`ls -ld "$PRG"`  
  11.   link=`expr "$ls" : '.*-> .$'`  
  12.   if expr "$link" : '/.*' > /dev/null; then  
  13.     PRG="$link"  
  14.   else  
  15.     PRG=`dirname "$PRG"`/"$link"  
  16.   fi  
  17. done  
  18.   
  19. PRGDIR=`dirname "$PRG"`  
  20. EXECUTABLE=catalina.sh  
  21.   
  22. # Check that target executable exists  
  23. if $os400; then  
  24.   # -x will Only work on the os400 if the files are:  
  25.   # 1. owned by the user  
  26.   # 2. owned by the PRIMARY group of the user  
  27.   # this will not work if the user belongs in secondary groups  
  28.   eval  
  29. else  
  30.   if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then  
  31.     echo "Cannot find $PRGDIR/$EXECUTABLE"  
  32.     echo "The file is absent or does not have execute permission"  
  33.     echo "This file is needed to run this program"  
  34.     exit 1  
  35.   fi  
  36. fi  
  37.   
  38. exec "$PRGDIR"/"$EXECUTABLE" start "$@"  

代码清单1中有两个主要的变量,分别是:

  • PRGDIR:当前shell脚本所在的路径;
  • EXECUTABLE:脚本catalina.sh。

根据最后一行代码:exec "$PRGDIR"/"$EXECUTABLE" start "$@",我们知道执行了shell脚本catalina.sh,并且传递参数start。catalina.sh中接收到start参数后的执行的脚本分支见代码清单2。

代码清单2

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. elif [ "$1" = "start" ] ; then  
  2.   
  3. # 此处省略参数校验的脚本  
  4.   
  5. shift  
  6. touch "$CATALINA_OUT"  
  7. if [ "$1" = "-security" ] ; then  
  8.   if [ $have_tty -eq 1 ]; then  
  9.     echo "Using Security Manager"  
  10.   fi  
  11.   shift  
  12.   eval "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \  
  13.     -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \  
  14.     -Djava.security.manager \  
  15.     -Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \  
  16.     -Dcatalina.base="\"$CATALINA_BASE\"" \  
  17.     -Dcatalina.home="\"$CATALINA_HOME\"" \  
  18.     -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \  
  19.     org.apache.catalina.startup.Bootstrap "$@" start \  
  20.     >> "$CATALINA_OUT" 2>&1 "&"  
  21.   
  22. else  
  23.   eval "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \  
  24.     -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \  
  25.     -Dcatalina.base="\"$CATALINA_BASE\"" \  
  26.     -Dcatalina.home="\"$CATALINA_HOME\"" \  
  27.     -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \  
  28.     org.apache.catalina.startup.Bootstrap "$@" start \  
  29.     >> "$CATALINA_OUT" 2>&1 "&"  
  30.   
  31. fi  
  32.   
  33. if [ ! -z "$CATALINA_PID" ]; then  
  34.   echo $! > "$CATALINA_PID"  
  35. fi  
  36.   
  37. echo "Tomcat started."  

从代码清单2可以看出,最终使用java命令执行了org.apache.catalina.startup.Bootstrap类中的main方法,参数也是start。Bootstrap的main方法的实现见代码清单3。

代码清单3

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Main method, used for testing only. 
  3.  * 
  4.  * @param args Command line arguments to be processed 
  5.  */  
  6. public static void main(String args[]) {  
  7.   
  8.     if (daemon == null) {  
  9.         // Don't set daemon until init() has completed  
  10.         Bootstrap bootstrap = new Bootstrap();  
  11.         try {  
  12.             bootstrap.init();  
  13.         } catch (Throwable t) {  
  14.             t.printStackTrace();  
  15.             return;  
  16.         }  
  17.         daemon = bootstrap;  
  18.     }  
  19.   
  20.     try {  
  21.         String command = "start";  
  22.         if (args.length > 0) {  
  23.             command = args[args.length - 1];  
  24.         }  
  25.   
  26.         if (command.equals("startd")) {  
  27.             args[args.length - 1] = "start";  
  28.             daemon.load(args);  
  29.             daemon.start();  
  30.         } else if (command.equals("stopd")) {  
  31.             args[args.length - 1] = "stop";  
  32.             daemon.stop();  
  33.         } else if (command.equals("start")) {  
  34.             daemon.setAwait(true);  
  35.             daemon.load(args);  
  36.             daemon.start();  
  37.         } else if (command.equals("stop")) {  
  38.             daemon.stopServer(args);  
  39.         } else {  
  40.             log.warn("Bootstrap: command \"" + command + "\" does not exist.");  
  41.         }  
  42.     } catch (Throwable t) {  
  43.         t.printStackTrace();  
  44.     }  
  45.   
  46. }  

从代码清单3可以看出,当传递参数start的时候,command等于start,此时main方法的执行步骤如下:

步骤一 初始化Bootstrap

  Bootstrap的init方法(见代码清单4)的执行步骤如下:

  1. 设置Catalina路径,默认为Tomcat的根目录;
  2. 初始化Tomcat的类加载器,并设置线程上下文类加载器(具体实现细节,读者可以参考《Tomcat7.0源码分析——类加载体系》一文);
  3. 用反射实例化org.apache.catalina.startup.Catalina对象,并且使用反射调用其setParentClassLoader方法,给Catalina对象设置Tomcat类加载体系的顶级加载器(Java自带的三种类加载器除外)。
代码清单4
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Initialize daemon. 
  3.  */  
  4. public void init()  
  5.     throws Exception  
  6. {  
  7.   
  8.     // Set Catalina path  
  9.     setCatalinaHome();  
  10.     setCatalinaBase();  
  11.   
  12.     initClassLoaders();  
  13.   
  14.     Thread.currentThread().setContextClassLoader(catalinaLoader);  
  15.   
  16.     SecurityClassLoad.securityClassLoad(catalinaLoader);  
  17.   
  18.     // Load our startup class and call its process() method  
  19.     if (log.isDebugEnabled())  
  20.         log.debug("Loading startup class");  
  21.     Class<?> startupClass =  
  22.         catalinaLoader.loadClass  
  23.         ("org.apache.catalina.startup.Catalina");  
  24.     Object startupInstance = startupClass.newInstance();  
  25.   
  26.     // Set the shared extensions class loader  
  27.     if (log.isDebugEnabled())  
  28.         log.debug("Setting startup class properties");  
  29.     String methodName = "setParentClassLoader";  
  30.     Class<?> paramTypes[] = new Class[1];  
  31.     paramTypes[0] = Class.forName("java.lang.ClassLoader");  
  32.     Object paramValues[] = new Object[1];  
  33.     paramValues[0] = sharedLoader;  
  34.     Method method =  
  35.         startupInstance.getClass().getMethod(methodName, paramTypes);  
  36.     method.invoke(startupInstance, paramValues);  
  37.   
  38.     catalinaDaemon = startupInstance;  
  39.   
  40. }  

步骤二 加载、解析server.xml配置文件

  当传递参数start的时候,会调用Bootstrap的load方法(见代码清单5),其作用是用反射调用catalinaDaemon(类型是Catalina)的load方法加载和解析server.xml配置文件,具体细节已在《TOMCAT源码分析——SERVER.XML文件的加载与解析》一文中详细介绍,有兴趣的朋友可以选择阅读。

 代码清单5

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Load daemon. 
  3.  */  
  4. private void load(String[] arguments)  
  5.     throws Exception {  
  6.   
  7.     // Call the load() method  
  8.     String methodName = "load";  
  9.     Object param[];  
  10.     Class<?> paramTypes[];  
  11.     if (arguments==null || arguments.length==0) {  
  12.         paramTypes = null;  
  13.         param = null;  
  14.     } else {  
  15.         paramTypes = new Class[1];  
  16.         paramTypes[0] = arguments.getClass();  
  17.         param = new Object[1];  
  18.         param[0] = arguments;  
  19.     }  
  20.     Method method =   
  21.         catalinaDaemon.getClass().getMethod(methodName, paramTypes);  
  22.     if (log.isDebugEnabled())  
  23.         log.debug("Calling startup class " + method);  
  24.     method.invoke(catalinaDaemon, param);  
  25.   
  26. }  

步骤三 启动Tomcat 

  当传递参数start的时候,调用Bootstrap的load方法之后会接着调用start方法(见代码清单6)启动Tomcat,此方法实际是用反射调用了catalinaDaemon(类型是Catalina)的start方法。

代码清单6

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Start the Catalina daemon. 
  3.  */  
  4. public void start()  
  5.     throws Exception {  
  6.     if( catalinaDaemon==null ) init();  
  7.   
  8.     Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);  
  9.     method.invoke(catalinaDaemon, (Object [])null);  
  10.   
  11. }  

Catalina的start方法(见代码清单7)的执行步骤如下:

  1. 验证Server容器是否已经实例化。如果没有实例化Server容器,还会再次调用Catalina的load方法加载和解析server.xml,这也说明Tomcat只允许Server容器通过配置在server.xml的方式生成,用户也可以自己实现Server接口创建自定义的Server容器以取代默认的StandardServer。
  2. 启动Server容器,有关容器的启动过程的分析可以参考《TOMCAT7.0源码分析——生命周期管理》一文的内容。
  3. 设置关闭钩子。这么说可能有些不好理解,那就换个说法。Tomcat本身可能由于所在机器断点,程序bug甚至内存溢出导致进程退出,但是Tomcat可能需要在退出的时候做一些清理工作,比如:内存清理、对象销毁等。这些清理动作需要封装在一个Thread的实现中,然后将此Thread对象作为参数传递给Runtime的addShutdownHook方法即可。
  4. 最后调用Catalina的await方法循环等待接收Tomcat的shutdown命令。
  5. 如果Tomcat运行正常且没有收到shutdown命令,是不会向下执行stop方法的,当接收到shutdown命令,Catalina的await方法会退出循环等待,然后顺序执行stop方法停止Tomcat。
代码清单7
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Start a new server instance. 
  3.  */  
  4. public void start() {  
  5.   
  6.     if (getServer() == null) {  
  7.         load();  
  8.     }  
  9.   
  10.     if (getServer() == null) {  
  11.         log.fatal("Cannot start server. Server instance is not configured.");  
  12.         return;  
  13.     }  
  14.   
  15.     long t1 = System.nanoTime();  
  16.   
  17.     // Start the new server  
  18.     try {  
  19.         getServer().start();  
  20.     } catch (LifecycleException e) {  
  21.         log.error("Catalina.start: ", e);  
  22.     }  
  23.   
  24.     long t2 = System.nanoTime();  
  25.     if(log.isInfoEnabled())  
  26.         log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");  
  27.   
  28.     try {  
  29.         // Register shutdown hook  
  30.         if (useShutdownHook) {  
  31.             if (shutdownHook == null) {  
  32.                 shutdownHook = new CatalinaShutdownHook();  
  33.             }  
  34.             Runtime.getRuntime().addShutdownHook(shutdownHook);  
  35.               
  36.             // If JULI is being used, disable JULI's shutdown hook since  
  37.             // shutdown hooks run in parallel and log messages may be lost  
  38.             // if JULI's hook completes before the CatalinaShutdownHook()  
  39.             LogManager logManager = LogManager.getLogManager();  
  40.             if (logManager instanceof ClassLoaderLogManager) {  
  41.                 ((ClassLoaderLogManager) logManager).setUseShutdownHook(  
  42.                         false);  
  43.             }  
  44.         }  
  45.     } catch (Throwable t) {  
  46.         // This will fail on JDK 1.2. Ignoring, as Tomcat can run  
  47.         // fine without the shutdown hook.  
  48.     }  
  49.   
  50.     if (await) {  
  51.         await();  
  52.         stop();  
  53.     }  
  54.   
  55. }  
Catalina的await方法(见代码清单8)实际只是代理执行了Server容器的await方法。
代码清单8
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Await and shutdown. 
  3.  */  
  4. public void await() {  
  5.   
  6.     getServer().await();  
  7.   
  8. }  
以Server的默认实现StandardServer为例,其await方法(见代码清单9)的执行步骤如下:
  1. 创建socket连接的服务端对象ServerSocket;
  2. 循环等待接收客户端发出的命令,如果接收到的命令与SHUTDOWN匹配(由于使用了equals,所以shutdown命令必须是大写的),那么退出循环等待。
代码清单9
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void await() {  
  2.     // Negative values - don't wait on port - tomcat is embedded or we just don't like ports gja  
  3.     if( port == -2 ) {  
  4.         // undocumented yet - for embedding apps that are around, alive.  
  5.         return;  
  6.     }  
  7.     if( port==-1 ) {  
  8.         whiletrue ) {  
  9.             try {  
  10.                 Thread.sleep( 10000 );  
  11.             } catch( InterruptedException ex ) {  
  12.             }  
  13.             if( stopAwait ) return;  
  14.         }  
  15.     }  
  16.       
  17.     // Set up a server socket to wait on  
  18.     ServerSocket serverSocket = null;  
  19.     try {  
  20.         serverSocket =  
  21.             new ServerSocket(port, 1,  
  22.                              InetAddress.getByName(address));  
  23.     } catch (IOException e) {  
  24.         log.error("StandardServer.await: create[" + address  
  25.                            + ":" + port  
  26.                            + "]: ", e);  
  27.         System.exit(1);  
  28.     }  
  29.   
  30.     // Loop waiting for a connection and a valid command  
  31.     while (true) {  
  32.   
  33.         // Wait for the next connection  
  34.         Socket socket = null;  
  35.         InputStream stream = null;  
  36.         try {  
  37.             socket = serverSocket.accept();  
  38.             socket.setSoTimeout(10 * 1000);  // Ten seconds  
  39.             stream = socket.getInputStream();  
  40.         } catch (AccessControlException ace) {  
  41.             log.warn("StandardServer.accept security exception: "  
  42.                                + ace.getMessage(), ace);  
  43.             continue;  
  44.         } catch (IOException e) {  
  45.             log.error("StandardServer.await: accept: ", e);  
  46.             System.exit(1);  
  47.         }  
  48.   
  49.         // Read a set of characters from the socket  
  50.         StringBuilder command = new StringBuilder();  
  51.         int expected = 1024// Cut off to avoid DoS attack  
  52.         while (expected < shutdown.length()) {  
  53.             if (random == null)  
  54.                 random = new Random();  
  55.             expected += (random.nextInt() % 1024);  
  56.         }  
  57.         while (expected > 0) {  
  58.             int ch = -1;  
  59.             try {  
  60.                 ch = stream.read();  
  61.             } catch (IOException e) {  
  62.                 log.warn("StandardServer.await: read: ", e);  
  63.                 ch = -1;  
  64.             }  
  65.             if (ch < 32)  // Control character or EOF terminates loop  
  66.                 break;  
  67.             command.append((char) ch);  
  68.             expected--;  
  69.         }  
  70.   
  71.         // Close the socket now that we are done with it  
  72.         try {  
  73.             socket.close();  
  74.         } catch (IOException e) {  
  75.             // Ignore  
  76.         }  
  77.   
  78.         // Match against our command string  
  79.         boolean match = command.toString().equals(shutdown);  
  80.         if (match) {  
  81.             log.info(sm.getString("standardServer.shutdownViaPort"));  
  82.             break;  
  83.         } else  
  84.             log.warn("StandardServer.await: Invalid command '" +  
  85.                                command.toString() + "' received");  
  86.   
  87.     }  
  88.   
  89.     // Close the server socket and return  
  90.     try {  
  91.         serverSocket.close();  
  92.     } catch (IOException e) {  
  93.         // Ignore  
  94.     }  
  95.   
  96. }  
至此,Tomcat启动完毕。很多人可能会问,执行sh shutdown.sh脚本时,是如何与Tomcat进程通信的呢?如果要与Tomcat的ServerSocket通信,socket客户端如何知道服务端的连接地址与端口呢?下面会慢慢说明。

停止过程分析

我们停止Tomcat的命令如下:
[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. sh shutdown.sh  
所以,将从shell脚本shutdown.sh开始分析Tomcat的停止过程。shutdown.sh的脚本代码见代码清单10。
代码清单10
[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. os400=false  
  2. case "`uname`" in  
  3. OS400*) os400=true;;  
  4. esac  
  5.   
  6. # resolve links - $0 may be a softlink  
  7. PRG="$0"  
  8.   
  9. while [ -h "$PRG" ] ; do  
  10.   ls=`ls -ld "$PRG"`  
  11.   link=`expr "$ls" : '.*-> .$'`  
  12.   if expr "$link" : '/.*' > /dev/null; then  
  13.     PRG="$link"  
  14.   else  
  15.     PRG=`dirname "$PRG"`/"$link"  
  16.   fi  
  17. done  
  18.   
  19. PRGDIR=`dirname "$PRG"`  
  20. EXECUTABLE=catalina.sh  
  21.   
  22. # Check that target executable exists  
  23. if $os400; then  
  24.   # -x will Only work on the os400 if the files are:  
  25.   # 1. owned by the user  
  26.   # 2. owned by the PRIMARY group of the user  
  27.   # this will not work if the user belongs in secondary groups  
  28.   eval  
  29. else  
  30.   if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then  
  31.     echo "Cannot find $PRGDIR/$EXECUTABLE"  
  32.     echo "The file is absent or does not have execute permission"  
  33.     echo "This file is needed to run this program"  
  34.     exit 1  
  35.   fi  
  36. fi  
  37.   
  38. exec "$PRGDIR"/"$EXECUTABLE" stop "$@"  
代码清单10和代码清单1非常相似,其中也有两个主要的变量,分别是:
  • PRGDIR:当前shell脚本所在的路径;
  • EXECUTABLE:脚本catalina.sh。
根据最后一行代码:exec "$PRGDIR"/"$EXECUTABLE" stop "$@",我们知道执行了shell脚本catalina.sh,并且传递参数stop。catalina.sh中接收到stop参数后的执行的脚本分支见代码清单11。
代码清单11
[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. elif [ "$1" = "stop" ] ; then  
  2.   
  3.   #省略参数校验脚本  
  4.   
  5.   eval "\"$_RUNJAVA\"" $LOGGING_MANAGER $JAVA_OPTS \  
  6.     -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \  
  7.     -Dcatalina.base="\"$CATALINA_BASE\"" \  
  8.     -Dcatalina.home="\"$CATALINA_HOME\"" \  
  9.     -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \  
  10.     org.apache.catalina.startup.Bootstrap "$@" stop  
从代码清单11可以看出,最终使用java命令执行了org.apache.catalina.startup.Bootstrap类中的main方法,参数是stop。从代码清单3可以看出,当传递参数stop的时候,command等于stop,此时main方法的执行步骤如下:

步骤一 初始化Bootstrap

  已经在启动过程分析中介绍, 不再赘述。

步骤二 停止服务

  通过调用Bootstrap的stopServer方法(见代码清单12)停止Tomcat,其实质是用反射调用catalinaDaemon(类型是Catalina)的stopServer方法。
代码清单12
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.   * Stop the standalone server. 
  3.   */  
  4.  public void stopServer(String[] arguments)  
  5.      throws Exception {  
  6.   
  7.      Object param[];  
  8.      Class<?> paramTypes[];  
  9.      if (arguments==null || arguments.length==0) {  
  10.          paramTypes = null;  
  11.          param = null;  
  12.      } else {  
  13.          paramTypes = new Class[1];  
  14.          paramTypes[0] = arguments.getClass();  
  15.          param = new Object[1];  
  16.          param[0] = arguments;  
  17.      }  
  18.      Method method =   
  19.          catalinaDaemon.getClass().getMethod("stopServer", paramTypes);  
  20.      method.invoke(catalinaDaemon, param);  
  21.   
  22.  }  
Catalina的stopServer方法(见代码清单13)的执行步骤如下:
  1. 创建Digester解析server.xml文件(此处只解析标签),以构造出Server容器(此时Server容器的子容器没有被实例化);
  2. 从实例化的Server容器获取Server的socket监听端口和地址,然后创建Socket对象连接启动Tomcat时创建的ServerSocket,最后向ServerSocket发送SHUTDOWN命令。根据代码清单9的内容,ServerSocket循环等待接收到SHUTDOWN命令后,最终调用stop方法停止Tomcat。
代码清单13
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void stopServer() {  
  2.     stopServer(null);  
  3. }  
  4.   
  5. public void stopServer(String[] arguments) {  
  6.   
  7.     if (arguments != null) {  
  8.         arguments(arguments);  
  9.     }  
  10.   
  11.     if( getServer() == null ) {  
  12.         // Create and execute our Digester  
  13.         Digester digester = createStopDigester();  
  14.         digester.setClassLoader(Thread.currentThread().getContextClassLoader());  
  15.         File file = configFile();  
  16.         try {  
  17.             InputSource is =  
  18.                 new InputSource("file://" + file.getAbsolutePath());  
  19.             FileInputStream fis = new FileInputStream(file);  
  20.             is.setByteStream(fis);  
  21.             digester.push(this);  
  22.             digester.parse(is);  
  23.             fis.close();  
  24.         } catch (Exception e) {  
  25.             log.error("Catalina.stop: ", e);  
  26.             System.exit(1);  
  27.         }  
  28.     }  
  29.   
  30.     // Stop the existing server  
  31.     try {  
  32.         if (getServer().getPort()>0) {   
  33.             Socket socket = new Socket(getServer().getAddress(),  
  34.                     getServer().getPort());  
  35.             OutputStream stream = socket.getOutputStream();  
  36.             String shutdown = getServer().getShutdown();  
  37.             for (int i = 0; i < shutdown.length(); i++)  
  38.                 stream.write(shutdown.charAt(i));  
  39.             stream.flush();  
  40.             stream.close();  
  41.             socket.close();  
  42.         } else {  
  43.             log.error(sm.getString("catalina.stopServer"));  
  44.             System.exit(1);  
  45.         }  
  46.     } catch (IOException e) {  
  47.         log.error("Catalina.stop: ", e);  
  48.         System.exit(1);  
  49.     }  
  50.   
  51. }  
最后,我们看看Catalina的stop方法(见代码清单14)的实现,其执行步骤如下:
  1. 将启动过程中添加的关闭钩子移除。Tomcat启动过程辛辛苦苦添加的关闭钩子为什么又要去掉呢?因为关闭钩子是为了在JVM异常退出后,进行资源的回收工作。主动停止Tomcat时调用的stop方法里已经包含了资源回收的内容,所以不再需要这个钩子了。
  2. 停止Server容器。有关容器的停止内容,请阅读《TOMCAT源码分析——生命周期管理》一文。
代码清单14
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /** 
  2.  * Stop an existing server instance. 
  3.  */  
  4. public void stop() {  
  5.   
  6.     try {  
  7.         // Remove the ShutdownHook first so that server.stop()   
  8.         // doesn't get invoked twice  
  9.         if (useShutdownHook) {  
  10.             Runtime.getRuntime().removeShutdownHook(shutdownHook);  
  11.   
  12.             // If JULI is being used, re-enable JULI's shutdown to ensure  
  13.             // log messages are not lost jiaan  
  14.             LogManager logManager = LogManager.getLogManager();  
  15.             if (logManager instanceof ClassLoaderLogManager) {  
  16.                 ((ClassLoaderLogManager) logManager).setUseShutdownHook(  
  17.                         true);  
  18.             }  
  19.         }  
  20.     } catch (Throwable t) {  
  21.         // This will fail on JDK 1.2. Ignoring, as Tomcat can run  
  22.         // fine without the shutdown hook.  
  23.     }  
  24.   
  25.     // Shut down the server  
  26.     try {  
  27.         getServer().stop();  
  28.     } catch (LifecycleException e) {  
  29.         log.error("Catalina.stop", e);  
  30.     }  
  31.   
  32. }  

总结

  通过对Tomcat源码的分析我们了解到Tomcat的启动和停止都离不开org.apache.catalina.startup.Bootstrap。当停止Tomcat时,已经启动的Tomcat作为socket服务端,停止脚本启动的Bootstrap进程作为socket客户端向服务端发送shutdown命令,两个进程通过共享server.xml里Server标签的端口以及地址信息打通了socket的通信。

http://blog.csdn.net/beliefer/article/details/51585006
0 0
原创粉丝点击