Tomcat详解(1)---Tomcat 的顶层结构及启动过程

1 Tomcat 的顶层结构及启动过程

1.1 Tomcat 的顶层结构

Tomcat 中最顶层的容器叫Server, 代表整个服务器
Server 中包含至少一个Service, 用于具体提供服务。
Service 包含两部分: Connector 和Container。

  • Connector 处理连接相关的事情,并提供Socket 与request 、response 的转换
  • Container用千封装和管理Servlet, 以及具体处理request 请求

一个Tomcat 中只有一个Server, 一个Server 可以包含多个Service
一个Service 只有一个Container, 但可以有多个Connectors (因为一个服务可以有多个连接,如同时提供http 和https 连接,也可以提供相同协议不同端口的连接)。
结构图如图7-1 所示。
这里写图片描述

Tomcat 里的Server 由org.apache.catalina.startup.Catalina 来管理, Catalina 是整个Tomcat的管理类,它里面的三个方法load 、start 、stop 分别用来管理整个服务器的生命周期。

  • load 方法用于根据conf/server.xml 文件创建Server 并调用Server 的init 方法进行初始化,
  • start方法用于启动服务器
  • stop 方法用于停止服务器,

  start 和stop 方法在内部分别调用了Server 的start和stop 方法, load 方法内部调用了Server 的init 方法,这三个方法都会按容器的结构逐层调用相应的方法,比如, Server 的start 方法中会调用所有的Service 中的start 方法。这就是Tomcat 生命周期的管理方式Catalina 还有个方法也很重要,那就是await 方法, Catalina 中的await 方法直接调用了Server 的await 方法,这个方法的作用是进入一个循环,让主线程不会退出。
  Tomcat 的入口main 方法在org.apache.catalina.startup.Bootstrap 中。Bootstrap 的作用类似一个CatalinaAdaptor, 具体处理过程还是使用Catalina 来完成的,这么做的好处是可以把启动的入口和具体的管理类分开,从而可以很方便地创建出多种启动方式,每种启动方式只需要写一个相应的CatalinaAdaptor 就可以了。

1.2 Bootstrap 的启动过程

  Bootstrap 是Tomcat 的入口,正常情况下启动Tomcat 就是调用的Bootstrap 的main 方法。

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) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            daemon = bootstrap;
        } else {
            // When running as a service the call to stop will be on a new
            // thread so make sure the correct class loader is used to prevent
            // a range of class not found exceptions.
            Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
        }

        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 if (command.equals("configtest")) {
                daemon.load(args);
                if (null==daemon.getServer()) {
                    System.exit(1);
                }
                System.exit(0);
            } else {
                log.warn("Bootstrap: command \"" + command + "\" does not exist.");
            }
        } catch (Throwable t) {
            // Unwrap the Exception for clearer error reporting
            if (t instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }

    }

  代码主要有两部分内容:

  • 新建了Bootstrap, 并执行init 方法初始化;
  • 处理main 方法传入的命令,如果args 参数为空,默认执行start 。

  在init 方法里初始化了ClassLoader, 并用ClassLoader 创建了Catalina 实例,然后赋给catalinaDaemon 变量,后面对命令的操作都要使用catalinaDaemon 来具体执行。
  对start 命令的处理调用了三个方法: setAwait(true) 、load(args) 和start() 。这三个方法内部都调用了Catalina 的相应方法进行具体执行,只不过是用反射来调用的。start 方法(另外两个方法会处理一些参数,调用方法类似)的代码如下:

/**
     * 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);

    }

  这里首先判断catalinaDaemon 有没有初始化, 如果没有则调用init 方法对其进行初始化,然后使用Method 进行反射调用Catalina 的start 方法。

1.3 Catalina 的启动过程

  Catalina 的启动主要是调用setAwait 、load 和start 方法来完成的。

  • setAwait 方法用于设置Server 启动完成后是再进入等待状态的标志,如果为true 则进入,否则不进入;
  • load方法用于加载配置文件,创建并初始化Se凹er;
  • start 方法用于启动服务器
    首先来看setAwait 方法,代码如下:
 public void setAwait(boolean b) {
        await = b;
    }

  Catalina 的load 方法根据conf/server.xml 创建了Server 对象,并赋值给server 属性(具体解析操作是通过开源项目Digester 完成的),然后调用了server 的init 方法,代码如下:

// Start the new server
        try {
            getServer().init();
        } catch (LifecycleException e) {
            if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
                throw new java.lang.Error(e);
            } else {
                log.error("Catalina.start", e);
            }

        }

  Catalina 的start 方法主要调用了server 的start 方法启动服务器,并根据await 属性判断是再让程序进入了等待状态,代码如下:

扫描二维码关注公众号,回复: 2804999 查看本文章
/**
     * 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.fatal(sm.getString("catalina.serverStartFail"), e);
            try {
                getServer().destroy();
            } catch (LifecycleException e1) {
                log.debug("destroy() failed for failed Server ", e1);
            }
            return;
        }

        long t2 = System.nanoTime();
        if(log.isInfoEnabled()) {
            log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
        }

        // 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);
            }
        }

        if (await) {
            await();
            stop();
        }
    }

  这里首先判断Server 是否已经存在了,如果不存在则调用load 方法来初始化Server ,然后调用Server 的start 方法来启动服务器,最后注册了关闭钩子并根据await 属性判断是否进入等待状态,之前我们己将这里的await 属性设置为true 了,所以需要进入等待状态。进入等待状态会调用await 和stop 两个方法, await 方法直接调用了Server的await 方法, Server的await 方法内部会执行一个while 循环,这样程序就停到了await 方法,当await 方法里的while 循环退州时,就会执行stop 方法,从而关闭服务器。

1.3 Server的启动过程

  Server 接口中提供addService(Service service)、removeService(Service service)来添加和删除Service, Server 的init 方法和start 方法分别循环调用了每个Service 的init 方法和start 方法来启动所有Service。
  Server 的默认实现是org.apache.cataline.core.StandardServer, StandardServer 继承自LifecycleMBeanBase,LifecycleMBeanBase 又继承自LifecycleBase, init 和start 方法就定义在了LifecycleBase中, LifecycleBase 里的init 方法和start 方法又调用initlnternal 方法和startInternal 方法,这两个方法都是模极方法,由子类具体实现,所以调用StandardServer 的init 和start 方法时会执行StandardServer 自己的initlntemal 和startlntemal 方法,这就是Tomcat 生命周期的管理方式。

  @Override
    protected void startInternal() throws LifecycleException {

        fireLifecycleEvent(CONFIGURE_START_EVENT, null);
        setState(LifecycleState.STARTING);

        globalNamingResources.start();

        // Start our defined Services
        synchronized (servicesLock) {
            for (int i = 0; i < services.length; i++) {
                services[i].start();
            }
        }
    }
 @Override
    protected void initInternal() throws LifecycleException {

        ....
        // Initialize our defined Services
        for (int i = 0; i < services.length; i++) {
            services[i].init();
        }
    }

  除了startlntemal 和initlnternal 方法, StandardServer 中还实现了await 方法, Catalina 中就是调用它让服务器进入等待状态的,其核心代码如下

/**
     * 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.
     */
    @Override
    public void await() {
        // Negative values - don't wait on port - tomcat is embedded or we just don't like ports
        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
                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
                }
            }
        }
    }

处理的大概逻辑是首先判断端口号port,然后根据port的值分为三种处理方法:

  • port为-2 ,则会直接退出,不进人循环。
  • port为-1 ,则会进入一个while ( !stopAwait)的循环,并且在内部没有break 跳出的语句. stopAwait 标志只有调用了stop 方法才会设置为true
  • port 为其他值,也会进入一个while ( !stopAwait)的循环,不过同时会在port所在端口启动一个ServerSocket 来监听关闭命令,如果接收到了则会使用break跳出循环。
    这里的端口port 和关闭命令shutdown 是在conf/server.xml 文件中配置Server 时设置的。
<Server port="8005" shutdown="SHUTDOWN">

这时会在8005 端口监昕”SHUTDOWN”命令,如果接收到了就会关闭Tomcat

1.4 Service 的启动过程

Service 的默认实现是org.apache.catalina.core.StandardService, StandardService 也继承自LifecycleMBeanBase 类,所以init 和start 方法最终也会调用initlntemal 和startlntemal 方法

@Override
    protected void initInternal() throws LifecycleException {

        super.initInternal();

        if (container != null) {
            container.init();
        }

        // Initialize any Executors
        for (Executor executor : findExecutors()) {
            if (executor instanceof LifecycleMBeanBase) {
                ((LifecycleMBeanBase) executor).setDomain(getDomain());
            }
            executor.init();
        }

        // Initialize our defined Connectors
        synchronized (connectorsLock) {
            for (Connector connector : connectors) {
                try {
                    connector.init();
                } catch (Exception e) {
                    String message = sm.getString(
                            "standardService.connector.initFailed", connector);
                    log.error(message, e);

                    if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
                        throw new LifecycleException(message);
                }
            }
        }
    }
@Override
    protected void startInternal() throws LifecycleException {

        if(log.isInfoEnabled())
            log.info(sm.getString("standardService.start.name", this.name));
        setState(LifecycleState.STARTING);

        // Start our defined Container first
        if (container != null) {
            synchronized (container) {
                container.start();
            }
        }

        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }

        // Start our defined Connectors second
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                try {
                    // If it has already failed, don't try and start it
                    if (connector.getState() != LifecycleState.FAILED) {
                        connector.start();
                    }
                } catch (Exception e) {
                    log.error(sm.getString(
                            "standardService.connector.startFailed",
                            connector), e);
                }
            }
        }
    }

可以看到, StandardService 中的initlntemal 和startlntemal 方法主要调用container 、executors 、mapperListener 、connectors 的init 和start 方法。mapperListener 是Mapper 的监听器,可以监听container 容器的变化, executors 是用在connectors中管理线程的线程池。,在serverx.xml 配置文件中有参考用法。

<Service name="Catalina">
    <Executor name="tomcatThreadPool" namePrefix =" cataline-exec-"
maxThreads =" 15 。" minSpareThr eads =" 4 "/>
<Connector executor="tomcatThreadPool"
    port="8080" protocol="HTTP/1.1 "
    connectionTimeout="20000" redirectPort="8443"/>
    .....
</Service>

这样Connector 就配置了一个叫tomcatThreadPool 的线程池,最多可以同时启动150 个线程,最少要有4 个可用线程。现在整个Tomcat 服务器就启动了,整个启动流程如图7-2 所示。
整个启动流程如图:
这里写图片描述

猜你喜欢

转载自blog.csdn.net/gchd19921992/article/details/79020161