zoukankan      html  css  js  c++  java
  • 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组件的管理。

  • 相关阅读:
    计算两个字符串的最大公共字串的长度,字符不区分大小写
    任何一个整数m的立方都可以写成m个连续奇数之和。
    求一个byte数字对应的二进制数字中1的最大连续数
    Elasticsearch的过滤查询
    如何在Elasticsearch中安装中文分词器(IK+pinyin)
    使用Linux的alternatives命令替换选择软件的版本
    PHP如何与搜索引擎Elasticsearch交互?
    如何安装搜索引擎Elasticsearch?
    如何修改MAC自带的PHP的版本?
    程序员技能图谱
  • 原文地址:https://www.cnblogs.com/cenyu/p/11072547.html
Copyright © 2011-2022 走看看