深入剖析Tomcat(十四) Server、Service 组件:如何启停Tomcat服务?

通过前面文章的学习,我们已经了解了连接器,四大容器是如何配合工作的,在源码中提供的示例也都是“一个连接器”+“一个顶层容器”的结构。并且启动方式是分别启动连接器和容器,类似下面代码

connector.setContainer(engine);
try {
    connector.initialize();
    ((Lifecycle) connector).start();
    ((Lifecycle) engine).start();

    // make the application wait until we press a key.
    System.in.read();
    ((Lifecycle) engine).stop();
} catch (Exception e) {
    e.printStackTrace();
}

连接器要运行起来需要执行两个方法 initialize() 与 start(),容器要运行起来只需执行一个方法 start()。

之前的设计存在两个问题:

1.Tomcat中不应该仅有支持HTTP协议的连接器,还应该有支持HTTPS等协议的连接器,所以连接器有多种类型。多个连接器可以关联同一个容器,不同的连接器将请求统一处理成容器需要的同一种对象即可,这种多对一的结构该如何设计?

2.之前章节的程序架构中,缺少一种关闭Tomcat的机制,仅仅是通过 System.in.read(); 来阻塞程序运行,还需要手动在控制台输入东西才能走关闭流程。

Tomcat设计了两个组件来解决上面两个问题:服务器组件(Server),服务组件(Service)。

Service组件

先看第一个问题,Tomcat提供了Service组件来将连接器容器包装起来,并向外提供 initialize() 与 start() 两个方法。他们之间的关系如下图所示

Service组件的标准实现类为StandardService,在StandardService的 initialize()方法中,调用了所有连接器的  initialize() 方法,代码如下

// 连接器数组
private Connector[] connectors = new Connector[0];

public void initialize() throws LifecycleException {
    if (initialized) throw new LifecycleException(sm.getString("standardService.initialize.initialized"));
    initialized = true;

    // Initialize our defined Connectors
    synchronized (connectors) {
        for (Connector connector : connectors) {
            connector.initialize();
        }
    }
}

StandardService的 start()方法中,调用了容器的start() 方法和所有连接器的 start() 方法,代码如下

private Connector[] connectors = new Connector[0];
private Container container = null;

public void start() throws LifecycleException {

    // Validate and update our current component state
    if (started) {
        throw new LifecycleException(sm.getString("standardService.start.started"));
    }

    // 通知事件监听器
    lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
    lifecycle.fireLifecycleEvent(START_EVENT, null);
    started = true;

    // 先启动容器
    if (container != null) {
        synchronized (container) {
            if (container instanceof Lifecycle) {
                ((Lifecycle) container).start();
            }
        }
    }

    // 再启动连接器
    synchronized (connectors) {
        for (Connector connector : connectors) {
            if (connector instanceof Lifecycle) {
                ((Lifecycle) connector).start();
            }
        }
    }

    // 通知事件监听器
    lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);

}

所以呢,Service组件的作用就是将连接器与容器的 initialize() 与 start() 两个方法的入口收束了一下。

Server组件

Tomcat中是支持多个Service组件实例的,如果存在多个Service组件实例的话,那么他们的 initialize() 与 start() 两个方法就又散开了,又要各启动各的,为了解决这个问题,Tomcat引入Server组件,将Service组件的 initialize() 与 start() 两个方法 再次收束一下,他们的结构如下图所示

Server组件的标准实现类为StandardServer,StandardServer的 initialize() 方法调用了所有Service组件的 initialize() 方法

private Service[] services = new Service[0];

public void initialize() throws LifecycleException {
    if (initialized) throw new LifecycleException(sm.getString("standardServer.initialize.initialized"));
    initialized = true;

    // 初始化所有Service组件
    for (int i = 0; i < services.length; i++) {
        services[i].initialize();
    }
}

StandardServer的 start() 方法调用了所有Service组件的 start() 方法

private Service[] services = new Service[0];

public void start() throws LifecycleException {

    // 防止重复指定start() 方法
    if (started) {
        throw new LifecycleException(sm.getString("standardServer.start.started"));
    }
    // 通知事件监听器
    lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
    lifecycle.fireLifecycleEvent(START_EVENT, null);
    started = true;

    // 启动所有Services
    synchronized (services) {
        for (Service service : services) {
            if (service instanceof Lifecycle) {
                ((Lifecycle) service).start();
            }
        }
    }

    // 通知事件监听器
    lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);

}

Server组件就是Tomcat的顶层组件了,上面提到的initialize() 和 start() 方法是启动Tomcat需要的两个方法,这两个方法执行完后,整个Tomcat服务就可以开始工作了。

什么情况下会用到多个Service组件实例呢?网上搜的内容看的云里雾里,总结起来就是:我们平时候开发基本不会用到多Service实例,所以这块的内容可以不求甚解😂。

接下来是如何关闭Tomcat服务

先来看关闭Tomcat服务需要调用的方法:StandardServer#stop(), 该方法通过调用所有Service组件的 stop() 方法进而层层调用各个组件和容器的 stop() 方法,将Tomcat服务正常关闭掉。

public void stop() throws LifecycleException {

    // Validate and update our current component state
    if (!started) {
        throw new LifecycleException(sm.getString("standardServer.stop.notStarted"));
    }

    // 通知事件监听器
    lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
    lifecycle.fireLifecycleEvent(STOP_EVENT, null);
    started = false;

    // 停止所有Service组件
    for (Service service : services) {
        if (service instanceof Lifecycle) {
            ((Lifecycle) service).stop();
        }
    }

    // 通知事件监听器
    lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);

}

如何触发stop() 方法的执行呢?

在之前的启动类中,start() 方法后会紧跟着 【System.in.read();】 来阻塞启动线程,并且 【System.in.read(); 】后紧跟着 stop() 方法。这个逻辑编排是没有问题的,主要就是这个 【System.in.read(); 】不像是个正常操作。StandardServer中提供了await() 方法来替代这个阻塞操作。

StandardServer的 await() 方法大致逻辑是这样:这个方法会创建一个ServerSocket,阻塞监听某个端口(默认8005),如果该ServerSocket收到Socket连接,并且接收到的消息是提前定义好的“关闭Tomcat”(shutdown)的指令时,该方法会结束阻塞并返回,否则会继续阻塞等待下一个请求。

也就是说 await() 通过一个TCP消息来达到结束阻塞的目的,比之前通过控制台输入字符来结束阻塞 高大上了很多。

await方法的代码如下

// 关闭Tomcat的指令
private String shutdown = "SHUTDOWN";

public void await() {

    // 创建一个server socket去阻塞等待
    ServerSocket serverSocket = null;
    try {
        serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
    } catch (IOException e) {
        System.err.println("StandardServer.await: create[" + port + "]: " + e);
        e.printStackTrace();
        System.exit(1);
    }

    // 循环等待链接并验证是不是shutdown命令
    while (true) {

        // 等待下一个链接
        Socket socket = null;
        InputStream stream = null;
        try {
            socket = serverSocket.accept();
            socket.setSoTimeout(10 * 1000);  // Ten seconds
            stream = socket.getInputStream();
        } catch (AccessControlException ace) {
            System.err.println("StandardServer.accept security exception: " + ace.getMessage());
            continue;
        } catch (IOException e) {
            System.err.println("StandardServer.await: accept: " + e);
            e.printStackTrace();
            System.exit(1);
        }

        // Read a set of characters from the socket
        StringBuffer command = new StringBuffer();
        int expected = 1024; // Cut off to avoid DoS attack
        while (expected < shutdown.length()) {
            if (random == null) {
                random = new Random(System.currentTimeMillis());
            }
            expected += (random.nextInt() % 1024);
        }
        while (expected > 0) {
            int ch = -1;
            try {
                ch = stream.read();
            } catch (IOException e) {
                System.err.println("StandardServer.await: read: " + e);
                e.printStackTrace();
                ch = -1;
            }
            if (ch < 32)  // Control character or EOF terminates loop
                break;
            command.append((char) ch);
            expected--;
        }

        // 关闭该socket连接
        try {
            socket.close();
        } catch (IOException e) {
            ;
        }

        // 判断收到的指令是不是shutdown指令,如果是则结束监听,否则继续阻塞监听
        boolean match = command.toString().equals(shutdown);
        if (match) {
            break;
        } else {
            System.err.println("StandardServer.await: Invalid command '" + command.toString() + "' received");
        }

    } // while end

    // 收到了shutdown命令,关闭 server socket 并返回
    try {
        serverSocket.close();
    } catch (IOException e) {
        ;
    }

}

await() 方法只是实现了一个阻塞逻辑,并提供了一个结束阻塞的方法。那么接下来只要将 await() 方法放在 start() 和 stop() 两个方法的中间即可。start() 方法执行后,Tomcat启动成功;接着await() 方法执行,启动线程进入阻塞状态;等到 await() 方法中的server socket收到关闭指令后,await() 方法结束阻塞并返回;接着就执行到stop() 方法,Tomcat就能正常关闭掉了。

这几个方法的逻辑编排如下图所示

下面是本章内容的启动类Bootstrap,与发送关闭Tomcat命令的工具类Stopper。

package ex14.pyrmont.startup;

import ex14.pyrmont.core.SimpleContextConfig;
import org.apache.catalina.Connector;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Server;
import org.apache.catalina.Service;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.http.HttpConnector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardServer;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.core.StandardWrapper;
import org.apache.catalina.loader.WebappLoader;

public final class Bootstrap {
  public static void main(String[] args) {

    System.setProperty("catalina.base", System.getProperty("user.dir"));
    Connector connector = new HttpConnector();

    Wrapper wrapper1 = new StandardWrapper();
    wrapper1.setName("Primitive");
    wrapper1.setServletClass("PrimitiveServlet");
    Wrapper wrapper2 = new StandardWrapper();
    wrapper2.setName("Modern");
    wrapper2.setServletClass("ModernServlet");

    Context context = new StandardContext();
    // StandardContext's start method adds a default mapper
    context.setPath("/app1");
    context.setDocBase("app1");

    context.addChild(wrapper1);
    context.addChild(wrapper2);

    LifecycleListener listener = new SimpleContextConfig();
    ((Lifecycle) context).addLifecycleListener(listener);

    Host host = new StandardHost();
    host.addChild(context);
    host.setName("localhost");
    host.setAppBase("webapps");

    Loader loader = new WebappLoader();
    context.setLoader(loader);
    // context.addServletMapping(pattern, name);
    context.addServletMapping("/Primitive", "Primitive");
    context.addServletMapping("/Modern", "Modern");

    Engine engine = new StandardEngine();
    engine.addChild(host);
    engine.setDefaultHost("localhost");

    Service service = new StandardService();
    service.setName("Stand-alone Service");
    Server server = new StandardServer();
    server.addService(service);
    service.addConnector(connector);

    //StandardService class's setContainer will call all its connector's setContainer method
    service.setContainer(engine);

    // Start the new server
    if (server instanceof Lifecycle) {
      try {
        server.initialize();
        ((Lifecycle) server).start();
        server.await();
        // the program waits until the await method returns,
        // i.e. until a shutdown command is received.
      }
      catch (LifecycleException e) {
        e.printStackTrace(System.out);
      }
    }

    // Shut down the server
    if (server instanceof Lifecycle) {
      try {
        ((Lifecycle) server).stop();
      }
      catch (LifecycleException e) {
        e.printStackTrace(System.out);
      }
    }
  }
}
package ex14.pyrmont.startup;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

public class Stopper {

    public static void main(String[] args) {
        // the following code is taken from the Stop method of
        // the org.apache.catalina.startup.Catalina class
        int port = 8005;
        try {
            Socket socket = new Socket("127.0.0.1", port);
            OutputStream stream = socket.getOutputStream();
            String shutdown = "SHUTDOWN";
            for (int i = 0; i < shutdown.length(); i++)
                stream.write(shutdown.charAt(i));
            stream.flush();
            stream.close();
            socket.close();
            System.out.println("The server was successfully shut down.");
        } catch (IOException e) {
            System.out.println("Error. The server has not been started.");
        }
    }
}

OK,这一章的内容就到这里。本章主要讲解了Server与Service两个组件,Server组件是Tomcat的顶层组件,它提供了启停Tomcat的方法。下一章来看一个更万无一失的关闭Tomcat的方案:关闭钩子(ShutdownHook)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/761910.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

DP V2.1a标准学习

一、说明 DP是DisplayPort的简写,是视频电子标准协会(VESA)标准化的数字式视频接口标准,可用于板内芯片之间的连接,也可用于输出接口连接外部设备。DisplayPort是一种基于数据包的可扩展协议,用于传输视频和音频数据。DisplayPort 具有高度可扩展性,并具有保持向后兼容…

【一步一步了解Java系列】:对这个系列的总结以及对缺漏内部类知识的补充

看到这句话的时候证明&#xff1a;此刻你我都在努力 加油陌生人 br />个人主页&#xff1a;Gu Gu Study专栏&#xff1a;一步一步了解Java 喜欢的一句话&#xff1a; 常常会回顾努力的自己&#xff0c;所以要为自己的努力留下足迹 喜欢的话可以点个赞谢谢了。 作者&#xf…

大模型微调新范式:当LoRA遇见MoE

©PaperWeekly 原创 作者 | 陈思硕 单位 | 北京大学 研究方向 | 自然语言处理 图片 当 LoRA 遇见 MoE&#xff0c;会擦出怎样的火花&#xff1f; 图片 ▲ 左侧&#xff1a;原始版本的 LoRA&#xff0c;权重是稠密的&#xff0c;每个样本都会激活所有参数&#xff1b;右…

第二节:如何使用thymeleaf渲染html(自学Spring boot 3.x的第一天)

大家好&#xff0c;我是网创有方&#xff0c;今天来学习如何使用thymeleaf渲染html。该模板运用不广泛&#xff0c;所以本节内容了解既可。 第一步&#xff1a;创建html文件。 在模板templates目录下创建一个html文件。 编写代码如下&#xff1a; <!DOCTYPE html> <…

Sentinel如何使用BlockExceptionHandler实现限流/降级错误页面显示

1、修改配置项&#xff0c;打开对Spring MVC端点的保护 spring.cloud.sentinel.filter.enabledtrue 2、编写 BlockExceptionHandler的实现类 MyUrlBlockHandler.java package com.codex.terry.sentinel.urlblockhandler;/*** 文件名称: MyUrlBlockHandler.java* 编写人: yh…

tf1问题记录

在复现一个开源项目https://github.com/macanv/BERT-BiLSTM-CRF-NER。有一个疑似TensorFlow-gpu、cudnn、cuda之间版本不兼容的问题。问题详情如下&#xff1a; 在base中输入nvidia-smi显示无此命令&#xff1a; 输入nvitop可正常显示&#xff1a; 输入nvcc -V显示为&#x…

机器学习——强化学习状态值函数V和动作值函数Q的个人思考

最近在回顾《西瓜书》的理论知识&#xff0c;回顾到最后一章——“强化学习”时对于值函数部分有些懵了&#xff0c;所以重新在网上查了一下&#xff0c;发现之前理解的&#xff0c;包括网上的大多数对于值函数的描述都过于学术化、公式化&#xff0c;不太能直观的理解值函数以…

SeeSR: Towards Semantics-Aware Real-World Image Super-Resolution

CVPR2024 香港理工大学&OPPO&bytedancehttps://github.com/cswry/SeeSR?tabreadme-ov-file#-licensehttps://arxiv.org/pdf/2311.16518#page5.80 问题引入 因为有些LR退化情况比较严重&#xff0c;所以超分之后的结果会出现语义的不一致的情况&#xff0c;所以本文训…

AI影像测量:开启测量仪器的智能之眼

在基于机器视觉的影像测量中&#xff0c;一些复杂特征传统测量需要人工手动选点测量&#xff0c;不仅易受到人为因素的干扰&#xff0c;而且极大的降低测量效率&#xff0c;提高了人力成本和生产成本。AI影像测量技术运用先进的机器视觉和深度学习算法&#xff0c;可快速、准确…

工程技术类SCI,低分快刊首选期刊,无版面费!

1、期刊概况 【期刊简介】IF&#xff1a;1.0-2.0&#xff0c;JCR2区&#xff0c;中科院4区&#xff1b; 【检索情况】SCIE在检 【版面类型】正刊&#xff0c;仅少量版面&#xff1b; 【出刊频率】年刊 2、征稿范围 本刊主要是发表有关能源转型和可再生能源需求相关的研究文…

如何用程序批量下载小红书的图片?

如何使用MediaCrawler快速下载图片 作为一名图像算法工程师&#xff0c;怎么能没有图片资源呢&#xff1f;今天&#xff0c;我要介绍一个能快速下载图片的方法&#xff0c;仅供学习使用&#xff0c;请勿用于其他用途。 下载项目 首先&#xff0c;从GitHub下载项目&#xff1…

Zabbix 排坑版 Centos7

systemctl stop firewalld;systemctl disable firewalld;setenforce 0sed -i s/SELINUXenforcing/SELINUXdisabled/ /etc/selinux/configzabbix源地址,可以自己选版本&#xff0c;安装都大差不差 rpm -Uvh https://repo.zabbix.com/zabbix/5.0/rhel/7/x86_64/zabbix-release-5…

Codeforces Round 955 (Div. 2) A~E

A.Soccer&#xff08;思维&#xff09; 题意&#xff1a; 迪马喜欢看足球比赛。在这样一场比赛中&#xff0c;记分牌上的比分表示为 x x x: y y y&#xff0c;其中 x x x是第一队的进球数&#xff0c; y y y是第二队的进球数。在任何时候&#xff0c;只有一支球队可以进球&am…

超声波清洗机怎么选?极力推荐四款口碑大牌超声波清洗机

相信大家都知道超声波清洗机&#xff0c;每次眼镜脏的时候&#xff0c;去眼镜店里让老板帮忙清洗&#xff0c;她们用的就是超声波清洗机&#xff0c;通过超声波的原理深入物品深处清洁&#xff0c;清洁效果非常好。相对手洗的方式&#xff0c;超声波清洗机能够保护镜片在清洗过…

jq实现拖动滑块实现人机校验——基础积累

最近在写后台管理系统&#xff0c;同事遇到一个需求关于滑动验证的。之前的样式是&#xff1a; 现在只要底部的滑动验证&#xff0c;图片不要了&#xff0c;而且要滑动到右边才算是验证通过。 就是如下所示的最简单的验证方式&#xff1a; 由于同事现有的项目是mvc的&#…

昇思25天学习打卡营第1天|yulang

今天主要了解了深度学习框架之昇思MindSpore的初学入门&#xff0c;没想到 ai学习入门如此简单&#xff0c;不愧是华为大手笔&#xff0c;提供的学习环境配置如此之高。这个平台有点类似百度飞桨&#xff0c;大大降低了AI开发门槛&#xff0c;使用户能够快速实现想要的模型&…

Dinky 让Flink作业纵享丝滑

1.Dinky是什么&#xff1f; Dinky 是一个开箱即用的一站式实时计算平台&#xff0c;以 Apache Flink 为基础&#xff0c;连接 OLAP 和数据湖等众多框架,致力于流批一体和湖仓一体的建设与实践。Dinky 让Flink作业纵享丝滑&#xff0c;为 Apache Flink 深度定制的新一代实时计算…

【Python机器学习】模型评估与改进——留一法交叉验证

留一法也是一种常见的交叉验证方法。 我们可以将留一法交叉验证看作是每折只包含单个样本的k折交叉验证。对于每次划分&#xff0c;选择单个数据点作为测试集。这种方法可能非常耗时&#xff0c;特征是对于大型数据&#xff0c;但是小型数据集上有时可以给出更好的估计结果&am…

生产环境部署Nginx服务器双机热备部署-keepalived(多种模式教程)

前言&#xff1a;今天演示下生产环境keepalived的部署方式&#xff0c;安装模式有很多&#xff0c;比如说主备模型和双主模型&#xff0c;主备分&#xff1a;抢占模式 和 非抢占模式。这里我会一一展开说具体怎么配置一、双节点均部署Nginx&#xff1a; 第一步&#xff1a;上传…

MSYS2教程(windows环境下使用linux工具)

MSYS2教程(windows环境下使用linux工具) 1.msys2简介 MSYS2&#xff08;Minimal SYStem 2&#xff09;是一个集成了大量的GNU工具链、工具和库的开源软件包集合。它提供了一个类似于Linux的shell环境&#xff0c;可以在Windows系统中编译和运行许多Linux应用程序和工具。 MS…