深入剖析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 Connectorssynchronized (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 stateif (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;// 啟動所有Servicessynchronized (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 stateif (!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 secondsstream = 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 socketStringBuffer command = new StringBuffer();int expected = 1024; // Cut off to avoid DoS attackwhile (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 loopbreak;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 mappercontext.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 methodservice.setContainer(engine);// Start the new serverif (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 serverif (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 classint 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.pswp.cn/web/37902.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/37902.shtml
英文地址,請注明出處:http://en.pswp.cn/web/37902.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

主流分布式消息中間件RabbitMQ、RocketMQ

分布式消息中間件在現代分布式系統中起著至關重要的作用。以下是一些主流的分布式消息中間件&#xff1a; 1. Apache Kafka - 特點&#xff1a;高吞吐量、低延遲、持久化、水平可擴展、分布式日志系統。 - 使用場景&#xff1a;日志收集與處理、實時流處理、事件驅動架構、大數…

NC204871 求和

鏈接 思路&#xff1a; 對于一個子樹來說&#xff0c;子樹的節點就包括在整顆樹的dfs序中子樹根節點出現的前后之間&#xff0c;所以我們先進行一次dfs&#xff0c;用b數組的0表示區間左端點&#xff0c;1表示區間右端點&#xff0c;同時用a數組來標記dfs序中的值。處理完dfs序…

小程序的運行機制、更新機制、生命周期介紹保姆級教程全解

一、小程序運行機制 1. 小程序冷啟動 小程序啟動可以分為兩種情況&#xff0c;一種是冷啟動&#xff0c;一種是熱啟動- 冷啟動&#xff1a;如果用戶首次打開&#xff0c;或小程序銷毀后被用戶再次打開&#xff0c;此時小程序需要重新加載啟動- 熱啟動&#xff1a;如果用戶已經打…

HSP_12章 Python面向對象編程oop_多態

文章目錄 P128 多態問題的引出P129 多態細節和使用1. 多態介紹&特別說明2. 多態的好處3. 特別說明: Python多態的特點4. 使用多態的機制來解決主人喂食物的問題 P128 多態問題的引出 先看一個問題 # 說明: 先試用傳統的方式完成 class Food:name Nonedef __init__(self,…

4.Android逆向協議-詳解二次打包失敗解決方案

免責聲明&#xff1a;內容僅供學習參考&#xff0c;請合法利用知識&#xff0c;禁止進行違法犯罪活動&#xff01; 內容參考于&#xff1a;微塵網校 上一個內容&#xff1a;3.Android逆向協議-APP反反編譯及回編譯 工具下載&#xff1a; 鏈接&#xff1a;https://pan.baidu.…

【MyBatis】 - 自定義TypeHandler-數組

在Java中&#xff0c;如果你使用的是MyBatis并需要為String數組自定義TypeHandler&#xff0c;可以按照以下步驟進行操作。TypeHandler用于自定義對象與數據庫字段之間的轉換。 步驟一&#xff1a;創建自定義的TypeHandler 首先&#xff0c;你需要創建一個自定義的TypeHandle…

#筆記# 寫給自己用的小爬蟲

最近完成了一個文旅行業信息聚合的小應用&#xff0c;實現僅從一個入口了解全行業的信息動態&#xff0c;不用一個一個翻看各網站&#xff0c;節省了不少檢索時間。 一、基本思路 明確數據來源。基于前述目標&#xff0c;確定數據源為文化和旅游部管理部門官網&#xff0c;比…

STM32中斷

目錄 stm32中斷原理標準庫高低電平使LED亮滅燈采用串口中斷方式做串口通信 stm32中斷原理 在STM32微控制器中&#xff0c;中斷是一種重要的事件驅動機制&#xff0c;用于處理實時事件而無需持續輪詢。中斷在處理外部事件&#xff08;如按鍵輸入、定時器溢出等&#xff09;時非…

【辦公類-21-18】20240701 養老護理員初級選擇題488,制作PyQt5圖形界面GUI

背景需求&#xff1a; 6月16日育嬰師高級考完了。運氣好&#xff0c;抽到的是”護理患腹瀉的幼兒”&#xff0c;“晨檢與家長溝通”&#xff0c;“4個月嬰兒喂蛋黃”&#xff0c;“21個月食譜”&#xff0c;都是我背過的題目&#xff08;沒有抽到感統&#xff09; 于是一放假&…

【C語言】解決C語言報錯:Invalid Pointer

文章目錄 簡介什么是Invalid PointerInvalid Pointer的常見原因如何檢測和調試Invalid Pointer解決Invalid Pointer的最佳實踐詳細實例解析示例1&#xff1a;未初始化的指針示例2&#xff1a;已釋放的指針示例3&#xff1a;返回局部變量的指針示例4&#xff1a;野指針 進一步閱…

three.js獲取深度圖

在Three.js中&#xff0c;獲取深度圖&#xff08;Depth Map&#xff09;通常涉及幾個步驟。深度圖是一個圖像&#xff0c;其中每個像素的值表示從攝像機到場景中相應點的距離。以下是如何在Three.js中獲取深度圖的基本步驟&#xff1a; 設置WebGLRenderer&#xff1a;確保你的T…

Android裁剪內核后編譯報錯compatibility matrix

【問題描述】&#xff1a; 優化開機速度&#xff0c;裁剪kernel&#xff0c;注釋掉模型模塊后如&#xff1a;# CONFIG_HID_SONY is not set&#xff0c;出現編譯報錯。 checkvintf E 07-01 16:32:02 160 160 check_vintf.cpp:620] files are incompatible: Runtime info a…

《化學工程與裝備》是什么級別的期刊?是正規期刊嗎?能評職稱嗎?

?問題解答 問&#xff1a;《化學工程與裝備》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知網收錄的第一批認定學術期刊。 問&#xff1a;《化學工程與裝備》級別&#xff1f; 答&#xff1a;省級。主管單位&#xff1a;福建石油化工集團有限責任公司 …

昇思25天學習打卡營第6天|網絡構建

網絡構建 概念模型模型參數 概念 神經網絡模型是由神經網絡層和Tensor操作構成的&#xff0c;mindspore.nn提供了常見神經網絡層的實現&#xff0c;在MindSpore中&#xff0c;Cell類是構建所有網絡的基類&#xff0c;也是網絡的基本單元。一個神經網絡模型表示為一個Cell&…

技術革新:如何用數據中臺實現數字化轉型

作為程序員&#xff0c;我們總是對技術如何改變企業運作充滿好奇。今天&#xff0c;我們將深入探討森馬集團如何利用數據中臺技術&#xff0c;實現從傳統數據分析到數字化轉型的華麗轉身。 1. 技術背景&#xff1a;森馬集團的數字化挑戰 森馬集團&#xff0c;一個在服飾行業占…

[單master節點k8s部署]8.pod健康探測

k8s默認的健康檢查機制是&#xff0c;每個容器都有一個監控進程&#xff0c;如果進程退出時返回碼非零&#xff0c;則認為容器發生故障。 存活探測 監測pod是否處于運行狀態&#xff0c;當liveness probe探測失敗的時候&#xff0c;根據重啟策略判斷是否需要重啟。適用于需要…

【Win測試】窗口捕獲的學習筆記

2 辨析筆記 2.1 mss&#xff1a;捕獲屏幕可見區域&#xff0c;不適合捕獲后臺應用 Claude-3.5-Sonnet: MSS庫可以用來捕獲屏幕上可見的內容&#xff1b;然而&#xff0c;如果游戲窗口被其他窗口完全遮擋或最小化&#xff0c;MSS將無法捕獲到被遮擋的游戲窗口內容&#xff0c;而…

天津惠靈頓:從心,致逐夢康橋|在這所天津國際學校從容不迫中走近夢想

在剛剛落下帷幕的申請季中&#xff0c;來自惠靈頓天津校區的Herman&#xff0c;陸續收到了劍橋大學、帝國理工學院、紐約大學、瓦薩學院等10余封錄取通知書。面對紛至沓來的名校肯定&#xff0c;經歷了短暫的塵埃落定的喜悅&#xff0c;Herman很快恢復了往日里的泰然自若。在他…

cv::Mat類的矩陣內容輸出的各種格式的例子

操作系統&#xff1a;ubuntu22.04OpenCV版本&#xff1a;OpenCV4.9IDE:Visual Studio Code編程語言&#xff1a;C11 功能描述 我們可以這樣使用&#xff1a;cv::Mat M(…); cout << M;&#xff0c;直接將矩陣內容輸出到控制臺。 輸出格式支持多種風格&#xff0c;包括O…

第5章:Electron加載與顯示內容(2)

5.4 加載和顯示不同類型的資源 Electron 支持加載和顯示多種類型的資源&#xff0c;包括圖片、視頻和其他靜態文件。 5.4.1 加載圖片的示例代碼 index.html&#xff1a; <!DOCTYPE html> <html> <head><title>Load Image</title> </head&…