• Tomcat组件梳理--Server


    Tomcat组件梳理--Server

    1.Server组件的定义和功能概述

    定义:

    Server组件用于描述一个启动的Tomcat实例,一个Tocmat被启动,在操作系统中占用一个进程号,提供web服务的功能,那个这个整个服务用Server来表示。

    功能

    Server作为描述一个Tomcat服务的组件,需要有对应的启动,停止方法,请求接收和处理方法等。所有的方法都是Server组件内部的一个子组件来实现。

    总结就是,Server代表Tomcat服务实例,Tomcat所有提供的功能,都由Server组件中的子组件来实现。

    2.Server组件的具体形式

    明白了Server是干嘛的,那么这个组件到底长什么样子呢?我们可以从代码里来看这个组件到底长什么样子,由什么组成。Server从抽象层面来看,代表了一个Tomcat实例,但落在代码中还是一个Java的Class类。因此,我们先来看看Server类到底有哪些属性,就知道这个组件长了什么样子。

    把Server类的代码中的属性抽取出来看如下:

    public final class StandardServer{
      //1.java命令和地址服务相关
        /**
         * JNDI的context
         */
        private javax.naming.Context globalNamingContext = null;
        /**
         * JNDI的resource
         */
        private NamingResourcesImpl globalNamingResources = null;
        /**
         * 作用与web application的JDNI监听器
         */
        private final NamingContextListener namingContextListener;
      
      //2.shutdown服务相关
          /**
         * 用于等待shutdown命令的端口号
         */
        private int port = 8005;
        /**
         * 用于等待shutdown命令的地址
         */
        private String address = "localhost";
          /**
         * shutdown服务在等待的命令
         */
        private String shutdown = "SHUTDOWN";
          /**
         * 执行await()的线程
         */
        private volatile Thread awaitThread = null;
    
        /**
         * 用于等待shutdown的socket对象
         */
        private volatile ServerSocket awaitSocket = null;
          /**
         * stopAwait的标记位
         */
        private volatile boolean stopAwait = false;
      
      //3.子组件Service相关
          /**
         * Server下的service集合
         */
        private Service services[] = new Service[0];
        /**
         * Service用到的锁
         */
        private final Object servicesLock = new Object();
      
     //4.父类加载器
          //父类加载器
        private ClassLoader parentClassLoader = null;
      
     //5.catalina相关的
          //catalinaHome的地址
        private File catalinaHome = null;
    
        //catalinaBase的地址
        private File catalinaBase = null;
    }
    

    如代码里展现出来的,长的样子主要从以下5个点来描述:

    • 1.JNDI相关
    • 2.停止公告shutdown相关
    • 3.子组件Service相关
    • 4.类加载器相关
    • 5.Catalina相关(Catalina作为Tomcat的启动组件,从这个类中启动Server)

    以上,Server的重要属性和特点都在这里了,当然还有很多功能都是在Service组件中进行定义的。这些等我们来做Service组件分析时,再详细说明。

    3.Server组件的具体功能

    在上面中,我们知道了Server的作用和重要属性,现在我们就来看看Server的重要功能。

    3.1.如何启动Server

    启动Server需要调用两个方法,在真正的start()方法之前还需要执行init()方法,init方法是一个pre-start()方法。

    看一下在Server中,这两个方法的具体表现。

    首先是init的方法。init方法里需要做5个逻辑处理,分别是:

    • 1.调用父类的init()方法
    • 2.注册全局的String cache,作用是什么现在还不太清楚
    • 3.注册MBean,使通过JMX能够监控
    • 4.调用JNDI的init方法
    • 5.对common和shared目录下的jar包进行校验,如果给出的jar文件包含MANIFEST,将被添加到container的manifest资源中.
    • 6.执行Service的init方法
     protected void initInternal() throws LifecycleException {
    
            //1.先调用父类的init方法
            super.initInternal();
       
            //2.注册全局的String cache,作用是什么现在还不清楚
            onameStringCache = register(new StringCache(), "type=StringCache");
    
            //3.注册JMX的MBean
            MBeanFactory factory = new MBeanFactory();
            factory.setContainer(this);
            onameMBeanFactory = register(factory, "type=MBeanFactory");
    
            //4.调用JNDI的init方法
            globalNamingResources.init();
    
            // Populate the extension validator with JARs from common and shared class loaders
       			//5.验证
            if (getCatalina() != null) {
                ClassLoader cl = getCatalina().getParentClassLoader();
                // Walk the class loader hierarchy. Stop at the system class loader.
                // This will add the shared (if present) and common class loaders
                while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
                    if (cl instanceof URLClassLoader) {
                        URL[] urls = ((URLClassLoader) cl).getURLs();
                        for (URL url : urls) {
                            if (url.getProtocol().equals("file")) {
                                try {
                                    File f = new File (url.toURI());
                                    if (f.isFile() &&
                                            f.getName().endsWith(".jar")) {
                                        ExtensionValidator.addSystemResource(f);
                                    }
                                } catch (URISyntaxException e) {
                                    // Ignore
                                } catch (IOException e) {
                                    // Ignore
                                }
                            }
                        }
                    }
                    cl = cl.getParent();
                }
            }
            //6.执行Service的init方法
            for (int i = 0; i < services.length; i++) {
                services[i].init();
            }
        }
    

    然后是start()方法,该方法主要处理以下3个逻辑:

    • 1.更新生命周期的状态,并发送生命周期时间,用来触发监听器
    • 2.启动JNDI
    • 3.启动Server中的所有Service组件。
        @Override
        protected void startInternal() throws LifecycleException {
    
            //1.发送生命周期时间
            fireLifecycleEvent(CONFIGURE_START_EVENT, null);
            //更改生命周期的状态为starting
            setState(LifecycleState.STARTING);
    
            //2.启动JDNI
            globalNamingResources.start();
    
            //3.调用Service的start()方法,启动Service
            synchronized (servicesLock) {
                for (int i = 0; i < services.length; i++) {
                    services[i].start();
                }
            }
        }
    

    3.2.如何关闭Server

    对Server的关闭,先提两个问题:1.应该在什么时候进行关闭,2.要如何关闭。

    第一个问题,Tomcat的解决方法时,Tomcat的main线程在启动完所有的组件后,自己开一个socket服务端,在指定的端口上进行监听,一直到有shutdown命令发送过来,就退出socket的等待,开始执行关闭方法。

    第二个问题,Server的关闭依旧放在Tomcat的生命周期中的stop方法和destroy方法中进行处理。

    虽然上面两个问题有解决方案了,但是应该还是有不清楚的地方,先看一下这两个问题在Catalina类中的调用顺序吧:

    //org.apache.catalina.startup.Catalina#start
    if (await) {
      await();
      stop();
    }
    

    如上,即判断一下是不是要await,如果需要,就调用Server的awati()方法开始循环等待socket连接,直到有一个匹配的命令行进来,然后结束await()方法的执行,开始执行stop()方法,开始处理停止相关的动作。

    先看第一个问题即await()在Server中的具体实现,该方法主要处理逻辑分4步:

    • 1.处理port等于-1或-2的情况。
    • 2.在指定的端口上开启一个socket的服务端
    • 3.在socket上进行等待连接,并对连接进行接收处理,与shutdown命令进行匹配,如果相等,就跳出循环。
    • 4.清理打开的资源。
    public void await() {
            //1.处理port等于-1或-2的情况。
            // 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;
            }
    
            //2.设置一个socket的服务端在指定端口上进行等待
            try {
                awaitSocket = new ServerSocket(port, 1,
                        InetAddress.getByName(address));
            } catch (IOException e) {
                return;
            }
    
            try {
                //当前线程设置为等待线程
                awaitThread = Thread.currentThread();
    
                //3.轮询等待,直到shutdown命令行进来,如果跟设置的匹配就结束循环
                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();
                        //3.1.等待连接,并拿到输入流inputSteam
                        socket = serverSocket.accept();
                        socket.setSoTimeout(10 * 1000);
                        stream = socket.getInputStream();
    
    
                        //3.2.有连接进来时,从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
                    //3.3.把传进来的字符串跟内置的进行匹配,如果如果一样就结束等待
                    boolean match = command.toString().equals(shutdown);
                  if (match) {
                 log.info(sm.getString("standardServer.shutdownViaPort"));
                        break;
                    }
    
                }
            //4.清理资源
            } finally {
                ServerSocket serverSocket = awaitSocket;
                awaitThread = null;
                awaitSocket = null;
    
                // Close the server socket and return
                if (serverSocket != null) {
                    try {
                        serverSocket.close();
                    } catch (IOException e) {
                        // Ignore
                    }
                }
            }
        }
    

    await()方法执行完之后,就自动开始调用stop()方法了,我们来开始看stop()方法里面的内容,注意,此时是在Catalina类中的stop方法

    // org.apache.catalina.startup.Catalina#stop
    // 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 {
        //调用Server的stop方法
        s.stop();
        //调用Server的destroy方法
        s.destroy();
      }
    } catch (LifecycleException e) {
      log.error("Catalina.stop", e);
    }
    

    这里应该比较容易看懂,Catalina的stop方法会调用Server的stop()方法和destroy()方法。

    此时再依次看Server的stop()方法,该方法主要处理4个逻辑:

    • 1.设置生命周期的转态,并推送生命周期的事件
    • 2.调用所有的service的stop方法
    • 3.停止JNDI
    • 4.清理扥带线程和socket。
    protected void stopInternal() throws LifecycleException {
    
      //1.设置生命周期的状态,
      setState(LifecycleState.STOPPING);
      fireLifecycleEvent(CONFIGURE_STOP_EVENT, null);
    
      // 2.调用所有的Service的stop方法
      for (int i = 0; i < services.length; i++) {
        services[i].stop();
      }
    
      //3.停止JDNI
      globalNamingResources.stop();
    
      //4.清理等待线程和socket
      stopAwait();
    }
    

    然后是destroy方法,该方法中有4个处理逻辑,

    • 1.调用所有的service的destroy()方法
    • 2.调用JNDI的destroy()
    • 3.解绑MBean,和String Cache,对应init方法中的注册。
    • 4.调用父类的方法
    protected void destroyInternal() throws LifecycleException {
      // 1.调用所有的service的destroy()方法
      for (int i = 0; i < services.length; i++) {
        services[i].destroy();
      }
    
      //2.调用JNDI的destroy()
      globalNamingResources.destroy();
    
      //3.解绑MBean,和String Cache,对应init方法中的注册。
      unregister(onameMBeanFactory);
    
      unregister(onameStringCache);
    
      //4.调用父类的方法
      super.destroyInternal();
    }
    

    经过上面这些步骤,整个Server的服务就被停止下来了。还有很多子组件,也会被停止,不过都是调用相同的生命周期,会在每个子组件中详细说。

    4.Server组件跟其他组件的联系

    现在,我们明白了Server组件自己的特有属性和自己的功能,但是他跟其他组件的联系是怎么样的?其实在上面两个动作中,已经能看到,主要有:Service组件,JDNI组件,JMX组件,类加载器组件。

    针对Service组件来说,一个Server组件包含多个Service,其实也可以看到,Server的主要功能,就是管理好所有的Service,逻辑都在Service中。

    JNDI组件现在不详细探索,先探索Tomcat主要组件,然后再解决这个问题。JMX组件同样。

    针对类加载器,Server只提供了一个设置和获取父类加载器的方法和属性,并没有提供自己的类加载器属性和方法,这里其实是有点不太懂的。

    5.总结

    以上,通过分析,我们知道Server主要是Tomcat服务的承担体,自己主要承担的是启动它下面的所有组件以及停止,以及对Service组件的管理。

  • 相关阅读:
    C#中 @ 的用法
    ASP.NET页面间传值
    ASP.NET中常用的文件上传下载方法
    把图片转换为字符
    把图片转换为字符
    JavaScript 时间延迟
    Using WSDLs in UCM 11g like you did in 10g
    The Definitive Guide to Stellent Content Server Development
    解决RedHat AS5 RPM安装包依赖问题
    在64位Windows 7上安装Oracle UCM 10gR3
  • 原文地址:https://www.cnblogs.com/cenyu/p/11072547.html
Copyright © 2020-2023  润新知