麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

Tomcat源碼分析——啟動與停止服務(wù)

2019-11-14 15:28:26
字體:
供稿:網(wǎng)友

前言

  熟悉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中有兩個主要的變量,分別是:

  • PRGDIR:當(dāng)前shell腳本所在的路徑;
  • EXECUTABLE:腳本catalina.sh。

根據(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í)行步驟如下:

  1. 設(shè)置Catalina路徑,默認(rèn)為Tomcat的根目錄;
  2. 初始化Tomcat的類加載器,并設(shè)置線程上下文類加載器(具體實(shí)現(xiàn)細(xì)節(jié),讀者可以參考《TOMCAT源碼分析——類加載體系》一文);
  3. 用反射實(shí)例化org.apache.catalina.startup.Catalina對象,并且使用反射調(diào)用其setParentClassLoader方法,給Catalina對象設(shè)置Tomcat類加載體系的頂級加載器(Java自帶的三種類加載器除外)。

代碼清單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í)行步驟如下:

  1. 驗(yàn)證Server容器是否已經(jīng)實(shí)例化。如果沒有實(shí)例化Server容器,還會再次調(diào)用Catalina的load方法加載和解析server.xml,這也說明Tomcat只允許Server容器通過配置在server.xml的方式生成,用戶也可以自己實(shí)現(xiàn)Server接口創(chuàng)建自定義的Server容器以取代默認(rèn)的StandardServer。
  2. 啟動Server容器,有關(guān)容器的啟動過程的分析可以參考《TOMCAT源碼分析——生命周期管理》一文的內(nèi)容。
  3. 設(shè)置關(guān)閉鉤子。這么說可能有些不好理解,那就換個說法。Tomcat本身可能由于所在機(jī)器斷點(diǎn),程序bug甚至內(nèi)存溢出導(dǎo)致進(jìn)程退出,但是Tomcat可能需要在退出的時候做一些清理工作,比如:內(nèi)存清理、對象銷毀等。這些清理動作需要封裝在一個Thread的實(shí)現(xiàn)中,然后將此Thread對象作為參數(shù)傳遞給Runtime的addShutdownHook方法即可。
  4. 最后調(diào)用Catalina的await方法循環(huán)等待接收Tomcat的shutdown命令。
  5. 如果Tomcat運(yùn)行正常且沒有收到shutdown命令,是不會向下執(zhí)行stop方法的,當(dāng)接收到shutdown命令,Catalina的await方法會退出循環(huán)等待,然后順序執(zhí)行stop方法停止Tomcat。

代碼清單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í)行步驟如下:

  1. 創(chuàng)建socket連接的服務(wù)端對象ServerSocket;
  2. 循環(huán)等待接收客戶端發(fā)出的命令,如果接收到的命令與SHUTDOWN匹配(由于使用了equals,所以shutdown命令必須是大寫的),那么退出循環(huán)等待。

代碼清單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非常相似,其中也有兩個主要的變量,分別是:

  • PRGDIR:當(dāng)前shell腳本所在的路徑;
  • EXECUTABLE:腳本catalina.sh。

根據(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í)行步驟如下:

  1. 創(chuàng)建Digester解析server.xml文件(此處只解析<Server>標(biāo)簽),以構(gòu)造出Server容器(此時Server容器的子容器沒有被實(shí)例化);
  2. 從實(shí)例化的Server容器獲取Server的socket監(jiān)聽端口和地址,然后創(chuàng)建Socket對象連接啟動Tomcat時創(chuàng)建的ServerSocket,最后向ServerSocket發(fā)送SHUTDOWN命令。根據(jù)代碼清單9的內(nèi)容,ServerSocket循環(huán)等待接收到SHUTDOWN命令后,最終調(diào)用stop方法停止Tomcat。

代碼清單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í)行步驟如下:

  1. 將啟動過程中添加的關(guān)閉鉤子移除。Tomcat啟動過程辛辛苦苦添加的關(guān)閉鉤子為什么又要去掉呢?因?yàn)殛P(guān)閉鉤子是為了在JVM異常退出后,進(jìn)行資源的回收工作。主動停止Tomcat時調(diào)用的stop方法里已經(jīng)包含了資源回收的內(nèi)容,所以不再需要這個鉤子了。
  2. 停止Server容器。有關(guān)容器的停止內(nèi)容,請閱讀《TOMCAT源碼分析——生命周期管理》一文。

代碼清單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);        }    }

總結(jié)

  通過對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

 

 


發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 欧美黄色三级视频 | 91精品视频免费 | 污黄视频在线播放 | 久久久久亚洲视频 | 91精品国产91热久久久做人人 | 中文字幕在线观看成人 | 一区二区精品视频 | 国产午夜电影在线观看 | 91香焦视频| 久久亚洲精品久久国产一区二区 | 国产精品视频一区二区三区四区国 | 国产亚洲美女精品久久久2020 | 日韩一级免费 | av国产片 | 在线视频观看成人 | 国产精品嘿咻嘿咻在线播放 | 成人在线视频免费 | 少妇一级淫片免费放播放 | 精品一二三区视频 | 成人激情视频网 | 欧美 亚洲 激情 | 日日噜噜夜夜爽 | 免费看日韩av | av在线1 | 国产免费福利视频 | 日韩精品久久久久久久电影99爱 | 视频一区二区不卡 | 中文在线观看www | 精品一二三区视频 | 欧美日韩在线视频一区二区 | 久久久久久久久久久久久久国产 | 黄色片在线免费播放 | 精品一区二区三区欧美 | 热99精品视频 | 黄色一级视频 | 视频久久免费 | 国产a级久久 | 中文字幕免费看 | 色猫av| 中文字幕观看 | 女人叉开腿让男人桶 |