Tomcat源码分析【八】启动过程分析之注册关闭钩子、阻塞监听关闭指令

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

文章首发于:clawhub.club


本篇主要分析Catalina的start方法内部的两块内容:注册关闭钩子与阻塞监听关闭指令。

注册关闭钩子

     // Register shutdown hook
            //注册关闭钩子
            if (useShutdownHook) {
                if (shutdownHook == null) {
                    // Catalina.this.stop();
                    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);
                }
            }

注册新的虚拟机来关闭钩子。
只是一个已初始化但尚未启动的线程。虚拟机开始启用其关闭序列时,它会以某种未指定的顺序启动所有已注册的关闭钩子,
并让它们同时运行。运行完所有的钩子后,如果已启用退出终结,那么虚拟机接着会运行所有未调用的终结方法。
最后,虚拟机会暂停。注意,关闭序列期间会继续运行守护线程,如果通过调用方法来发起关闭序列,
那么也会继续运行非守护线程。

注册关闭钩子,CatalinaShutdownHook,当关闭时,会执行CatalinaShutdownHook中的方法:

     @Override
            public void run() {
                try {
                    if (getServer() != null) {
                        Catalina.this.stop();
                    }
                } catch (Throwable ex) {
                    ExceptionUtils.handleThrowable(ex);
                    log.error(sm.getString("catalina.shutdownHookFail"), ex);
                } finally {
                    // If JULI is used, shut JULI down *after* the server shuts down
                    // so log messages aren't lost
                    LogManager logManager = LogManager.getLogManager();
                    if (logManager instanceof ClassLoaderLogManager) {
                        ((ClassLoaderLogManager) logManager).shutdown();
                    }
                }
            }
        }

调用 Catalina.this.stop();

    /**
         * 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
                    LogManager logManager = LogManager.getLogManager();
                    if (logManager instanceof ClassLoaderLogManager) {
                        ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                                true);
                    }
                }
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                // This will fail on JDK 1.2. Ignoring, as Tomcat can run
                // fine without the shutdown hook.
            }

            // Shut down the server
            try {
                Server s = getServer();
                LifecycleState state = s.getState();
                if (LifecycleState.STOPPING_PREP.compareTo(state) <= 0
                        && LifecycleState.DESTROYED.compareTo(state) >= 0) {
                    // Nothing to do. stop() was already called
                } else {
                    s.stop();
                    s.destroy();
                }
            } catch (LifecycleException e) {
                log.error("Catalina.stop", e);
            }

        }

移除钩子,调用Server组件的stop、destroy生命周期方法,这个我打算后期专门分析。

阻塞监听关闭指令

    // Bootstrap中会设置await为true,其目的在于让tomcat在shutdown端口阻塞监听关闭命令
            if (await) {
                //等待收到正确的关机命令,然后返回。
                await();
                //停止现有的服务器实例。
                stop();
            }

主要看StandardServer的await方法:

    /**
         * Wait until a proper shutdown command is received, then return.
         * 等待收到正确的关机命令,然后返回。
         * This keeps the main thread alive - the thread pool listening for http
         * connections is daemon threads.
         * 这使主线程保持活动状态——侦听http连接的线程池是守护进程线程。
         */
        @Override
        public void await() {
            // Negative values - don't wait on port - tomcat is embedded or we just don't like ports
            //负值——不要等待端口——tomcat是嵌入式的,或者我们只是不喜欢端口
            if (port == -2) {
                // undocumented yet - for embedding apps that are around, alive.
                // 还没有正式文件-为嵌入式应用程序周围,活着。
                return;
            }
            if (port == -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(port, 1,
                        InetAddress.getByName(address));
            } catch (IOException e) {
                log.error("StandardServer.await: create[" + address
                        + ":" + port
                        + "]: ", 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
                    //"SHUTDOWN"
                    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
                    }
                }
            }
        }

直到接收到"SHUTDOWN"指令,否则一直循环。

至此,简单的分析完了Tomcat的启动过程,这里简单概述一下:

  • server.xml配置文件解析,组装Catalina实例,注入Server\Service\Connector\Engine\Host\Context等组件。
  • 执行各个组件的生命周期函数init,做初始化操作,通知监听器等。
  • 执行各组件的start方法,最重要是Connector的start
  • Connector开启服务Socket,创建并启动acceptor, poller线程等。
  • 注册关闭钩子与阻塞监听关闭指令。

之后的文章分析如何处理客户端的请求。


来源:https://www.jianshu.com/u/9632919f32c3

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Tomcat源码分析【八】启动过程分析之注册关闭钩子、阻塞监听关闭指令

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏