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

news2024/11/16 13:37:44

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

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)。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MATLAB|更改绘图窗口的大小和位置

MATLAB绘图 plot、plot3、cdfplot都适用 效果 如下图&#xff0c;运行程序后可以直接得到这两个绘图窗口。 右上角的Figure1是原始图片&#xff0c;右下角的Figure2是调整了位置和大小后的绘图窗口。 完整源代码 % 绘图大小和位置调整 % Evand©2024 % 2024-7-1/Ver1…

代码随想录算法训练营第59天:动态[1]

代码随想录算法训练营第59天&#xff1a;动态 两个字符串的删除操作 力扣题目链接(opens new window) 给定两个单词 word1 和 word2&#xff0c;找到使得 word1 和 word2 相同所需的最小步数&#xff0c;每步可以删除任意一个字符串中的一个字符。 示例&#xff1a; 输入: …

MySQL 常见存储引擎详解(一)

本篇主要介绍MySQL中常见的存储引擎。 目录 一、InnoDB引擎 简介 特性 最佳实践 创建InnoDB 存储文件 二、MyISAM存储引擎 简介 特性 创建MyISAM表 存储文件 存储格式 静态格式 动态格式 压缩格式 三、MEMORY存储引擎 简介 特点 创建MEMORY表 存储文件 内…

【postgresql】版本学习

PostgreSQL 17 Beta 2 发布于2024-06-27。 PostgreSQL 17 Beta 2功能和变更功能的完整列表&#xff1a;PostgreSQL: Documentation: 17: E.1. Release 17 ​ 支持的版本&#xff1a; 16 ( 当前版本) / 15 / 14 / 13 / 12 ​ 不支持的版本&#xff1a; 11 / 10 / 9.6 / 9.5 /…

UE4_材质_材质节点_Fresnel

学习笔记&#xff0c;不喜勿喷&#xff0c;侵权立删&#xff0c;祝愿生活越来越好&#xff01; 一、问题导入 在创建电影或过场动画时&#xff0c;你常常需要想办法更好地突显角色或场景的轮廓。这时你需要用到一种光照技术&#xff0c;称为边沿光照或边缘光照&#xff0c;它…

Spring Cloud Circuit Breaker基础入门与服务熔断

官网地址&#xff1a;https://spring.io/projects/spring-cloud-circuitbreaker#overview 本文SpringCloud版本为&#xff1a; <spring.boot.version>3.1.7</spring.boot.version> <spring.cloud.version>2022.0.4</spring.cloud.version>【1】Circu…

为何交易价格可能超出预期?

当你尝试执行订单时&#xff0c;如果收到“报价超出”的提示&#xff0c;这通常意味着交易无法按你的预期价格成交。对于某些交易者来说&#xff0c;这可能会带来一些困扰&#xff0c;但在外汇等流动性极高的市场中&#xff0c;这种情况是相当常见的。 外汇市场之所以吸引众多…

HttpServletResponse设置headers返回,发现headers中缺少“Content-Length“和“Content-Type“两个参数。

业务中需要将用httpUtils请求返回的headers全部返回&#xff0c;塞到HttpServletResponse中&#xff0c;代码如下&#xff1a; HttpServletResponse response;// 返回headers Arrays.stream(httpResponse.getHeaders()).forEach(header -> response.setHeader(header.getNa…

鼠标连点器如何用?电脑鼠标连点器教程!超详细!

鼠标连点器&#xff0c;作为一种辅助工具&#xff0c;在日常生活和工作中扮演着越来越重要的角色。它能够模拟人工点击鼠标的动作&#xff0c;实现自动化操作&#xff0c;极大地提高了工作效率&#xff0c;并解决了许多重复性工作的烦恼。下面&#xff0c;我们将详细探讨金舟鼠…

【吴恩达机器学习-week2】可选实验:特征工程和多项式回归【Feature Engineering and Polynomial Regression】

支持我的工作 &#x1f389; 如果您想看到更详细、排版更精美的该系列文章&#xff0c;请访问&#xff1a;2022吴恩达机器学习Deeplearning.ai课程作业 可选实验&#xff1a;特征工程和多项式回归 目标 在本次实验中&#xff0c;你将&#xff1a;探索特征工程和多项式回归&a…

SpringBoot:SpringBoot中调用失败如何重试

一、引言 在实际的应用中&#xff0c;我们经常需要调用第三方API来获取数据或执行某些操作。然而&#xff0c;由于网络不稳定、第三方服务异常等原因&#xff0c;API调用可能会失败。为了提高系统的稳定性和可靠性&#xff0c;我们通常会考虑实现重试机制。 Spring Retry为Spri…

LinkedHashMap、TreeMap

LinkedHashMap&#xff1a; 有序、不重复、无索引&#xff0c;底层是双链表 TreeMap&#xff1a;底层基于红黑树&#xff0c;可以对键进行排序 默认排序&#xff1a;integer和string都是从小到大排序 例题&#xff1a;

基于SpringBoot扶农助农政策管理系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

Qt:7.QWidget属性介绍(cursor属性-光标形状、font属性-控件文本样式、tooltip属性-控件提示信息)

目录 一、cursor属性-光标形状&#xff1a; 1.1cursor属性介绍&#xff1a; 1.2获取当前光标形状——cursor()&#xff1a; 1.3 设置光标的形状——setCursor()&#xff1a; 1.4 设置自定义图片为光标&#xff1a; 二、font属性-控件文本样式&#xff1a; 2.1font属性介绍…

什么样的网工才是有前途的?

最近整个就业市场的变化&#xff0c;搞得人心惶惶。 可能很多朋友都在思考这样一个问题&#xff1a;现在做网工还有前途吗&#xff1f;什么样的网工才是有前途的&#xff1f;考HCIE认证还来得及吗&#xff1f; 作为网络工程师&#xff0c;该如何确保自己的职业发展方向正确&a…

光荚含羞草基因组-文献精读26

Haplotype-resolved genome of Mimosa bimucronata revealed insights into leaf movement and nitrogen fixation 光荚含羞草单倍型解析基因组揭示了叶片运动和固氮的相关机制 摘要 背景 光荚含羞草起源于热带美洲&#xff0c;具有独特的叶片运动特征&#xff0c;其运动速度…

C语言之进程的学习2

Env环境变量&#xff08;操作系统的全局变量&#xff09;

2024年7月1日 (周一) 叶子游戏新闻

老板键工具来唤去: 它可以为常用程序自定义快捷键&#xff0c;实现一键唤起、一键隐藏的 Windows 工具&#xff0c;并且支持窗口动态绑定快捷键&#xff08;无需设置自动实现&#xff09;。 喜马拉雅下载工具: 字面意思 《星刃》早期概念图分享 末世破败环境推主Genki分享了《星…

泛微E9开发 限制明细表列的值重复

限制明细表列的值重复 1、需求说明2、实现方法3、扩展知识点3.1 修改单个字段值&#xff08;不支持附件类型&#xff09;3.1.1 格式3.1.2 参数3.1.3 案例 3.2 获取明细行所有行标示3.2.1 格式3.2.2 参数说明 1、需求说明 限制明细表的“类型”字段&#xff0c;在同一个流程表单…

海康视频播放,包含h5和web插件

自行下载 海康开放平台 demo 都写得很清楚&#xff0c;不多描述 1.视频web插件 vue2写法&#xff0c;公共vue文件写法&#xff0c;调用文件即可 开始时需要以下配置&#xff0c;不知道的找对接平台数据的人&#xff0c;必须要&#xff0c;否则播不了 getParameterData: {po…