2019-10-28 17:45  阅读(2213)
文章分类:Tomcat 源码分析 文章标签:TomcatTomcat 源码
©  原文作者:clawhub 原文地址:http://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)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> Tomcat源码分析【八】启动过程分析之注册关闭钩子、阻塞监听关闭指令
上一篇
Tomcat源码分析【七】启动过程分析之Server组件的start方法
下一篇
Tomcat源码分析【九】Connector连接器