tomcat(14)服务器组件和服务组件
                                                            生活随笔
收集整理的這篇文章主要介紹了
                                tomcat(14)服务器组件和服务组件
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.                        
                                
                            
                            
                            【0】README
 
0.1)本文部分文字描述轉自 “how tomcat works”,旨在學習?“tomcat(14)服務器組件和服務組件”?的基礎知識;
 
0.2)for complete source code ,please visit ?https://github.com/pacosonTang/HowTomcatWorks/tree/master/chapter14
 
【1】服務器組件(org.apache.catalina.Server) 1)org.apache.catalina.Server接口的實例表示 Catalina的整個servlet引擎,囊括了所有的組件。服務器組件使用了一種優雅的方法來啟動/關閉 整個系統,不需要再對連接器和容器分別啟動/ 關閉; 2)啟動/關閉機制的工作原理:當啟動服務器組件時,它會啟動其中所有的組件,然后它就無限期地等待關閉命令。如果想要關閉系統,可以向指定端口發送一條關閉命令。服務器組件接收到關閉命令后,就會關閉其中所有的組件; 3)服務組件:服務器組件引入了服務組件來包含其他組件,如一個容器組件和一個或多個連接器組件;(干貨——注意區分服務器組件和服務組件,以下代碼摘自某測試用例,StandardService表服務組件,而StandardServer表服務器組件) Service service = new StandardService();service.setName("Stand-alone Service");Server server = new StandardServer();server.addService(service);
4)服務器組件Server接口定義 package org.apache.catalina; import org.apache.catalina.deploy.NamingResources; public interface Server { public String getInfo();public NamingResources getGlobalNamingResources(); public void setGlobalNamingResources (NamingResources globalNamingResources); public int getPort(); public void setPort(int port); public String getShutdown(); public void setShutdown(String shutdown); public void addService(Service service); public void await(); public Service findService(String name); public Service[] findServices(); public void removeService(Service service);public void initialize() throws LifecycleException; } 對以上代碼的分析(Analysis): A1)shutdown屬性:保存了必須發送給Server 實例用來關閉整個系統的關閉命令; A2)port屬性:定義了服務器組件會從哪個端口獲取關閉命令; A3)addService() 和 removeService()方法:為服務器組件添加或移除服務組件; A4)findService()方法:返回添加到該服務器組件中的所有服務組件; A5)initialize()方法:包含在系統啟動前要執行的一些代碼;
【2】StandardServer類 1)intro:一個服務器組件可以有0個或多個服務組件;(干貨——一個服務器組件可以有0個或多個服務組件) 2)StandardServer類有4個與生命周期相關的方法,分別是initialize()方法,start()方法,stop()方法 和 await()方法;
【2.1】initialize()方法 1)intro:用于初始化添加到其中的服務器組件; public void initialize() throws LifecycleException { //org.apache.catalina.StandardServer.initialize().if (initialized)throw new LifecycleException (sm.getString("standardServer.initialize.initialized"));initialized = true;// Initialize our defined Servicesfor (int i = 0; i < services.length; i++) {services[i].initialize();}} 【2.2】start() 方法 1)intro:該方法用于啟動服務器組件,即啟動所有的服務組件,逐個啟動所有組件,如連接器組件和servlet容器; public void start() throws LifecycleException { //org.apache.catalina.StandardServer.start().// Validate and update our current component stateif (started)throw new LifecycleException(sm.getString("standardServer.start.started"));// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);lifecycle.fireLifecycleEvent(START_EVENT, null);started = true;// Start our defined Servicessynchronized (services) {for (int i = 0; i < services.length; i++) {if (services[i] instanceof Lifecycle)((Lifecycle) services[i]).start();}}// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);} 【2.3】stop()方法 1)intro:用于關閉服務器組件;(調用stop方法會關閉所有的服務組件,并reset started,這樣,才能再次啟動服務器組件;參見start()方法) public void stop() throws LifecycleException { //org.apache.catalina.StandardServer.stop().// Validate and update our current component stateif (!started)throw new LifecycleException(sm.getString("standardServer.stop.notStarted"));// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);lifecycle.fireLifecycleEvent(STOP_EVENT, null);started = false;// Stop our defined Servicesfor (int i = 0; i < services.length; i++) {if (services[i] instanceof Lifecycle)((Lifecycle) services[i]).stop();}// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);} 【2.4】await()方法 1)intro:該方法負責等待關閉整個tomcat 部署的命令; 2)await()方法:調用該方法后,會一直阻塞,直到它從 8085 端口(也可以是其他端口)上接收到關閉命令(accept()方法);當await()方法返回時,會運行stop方法來關閉其下的所有子組件; public void await() { //org.apache.catalina.StandardServer.await().// Set up a server socket to wait onServerSocket 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);}// Loop waiting for a connection and a valid commandwhile (true) {// Wait for the next connectionSocket socket = null;InputStream stream = null;try {socket = serverSocket.accept(); //highlight line.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--;}// Close the socket now that we are done with ittry {socket.close();} catch (IOException e) {;}// Match against our command stringboolean match = command.toString().equals(shutdown); //highlight line.if (match) {break;} elseSystem.err.println("StandardServer.await: Invalid command '" +command.toString() + "' received");}// Close the server socket and returntry {serverSocket.close();} catch (IOException e) {;}} 對以上代碼的調用過程分析(steps): step1)創建一個ServerSocket對象,監聽8085端口,并在while循環中調用它的accept()方法; step2)當在指定端口上接收到消息時,accept()方法才會返回; step3)將接收到的消息與關閉命令的字符串相比較,相同的話就跳出while循環,關閉 ServerSocket,否則會再次循環,繼續等待消息;
【3】Service接口(服務組件接口——org.apache.catalina.core.Service) 1)intro:一個服務組件可以有一個servlet容器 和 多個連接器實例;(干貨——一個服務器組件可以有0個或多個服務組件,而一個服務組件可以有一個servlet容器 和 多個連接器實例) 2)可以自由地把連接器添加到服務組件中,所有的連接器都會與這個servlet容器相關聯;? package org.apache.catalina; public interface Service { public Container getContainer(); public void setContainer(Container container); public String getInfo(); public String getName(); public void setName(String name); public Server getServer(); public void setServer(Server server); public void addConnector(Connector connector); public Connector[] findConnectors(); public void removeConnector(Connector connector); public void initialize() throws LifecycleException; } 【4】StandardService類(org.apache.catalina.core.StandardService) 1)StandardService.initialize()方法:用于初始化添加到其中的所有連接器; 2)StandardService.start()方法:啟動連接器和所有servlet容器;
【4.1】connectors 和 container 1)StandardService實例中有兩種組件:連接器和servlet容器;其中servlet容器只有一個,而連接器則可以有多個;(干貨——StandardService實例中有兩種組件:連接器和servlet容器) 2)多個連接器使tomcat 可以為多種不同的請求協議提供服務;如,一個連接器處理HTTP請求,而另一個連接器處理HTTPS請求;(干貨——多個連接器使tomcat 可以為多種不同的請求協議提供服務) 3)變量connectors 和 container:StandardService類使用 container 變量來指向一個Container接口的實例,而數組connectors 來保存所有連接器的引用;(干貨——變量connectors 和 container的用途) private Connector connectors[] = new Connector[0]; // defined in StandardService. 4)setContainer()方法:需要調用setContainer()方法 將servlet容器與服務組件相關聯; public void setContainer(Container container) { // org.apache.catalina.core.StandardService.setContainer().Container oldContainer = this.container;if ((oldContainer != null) && (oldContainer instanceof Engine))((Engine) oldContainer).setService(null);this.container = container;if ((this.container != null) && (this.container instanceof Engine))((Engine) this.container).setService(this);if (started && (this.container != null) &&(this.container instanceof Lifecycle)) {try {((Lifecycle) this.container).start();} catch (LifecycleException e) {;}}synchronized (connectors) {for (int i = 0; i < connectors.length; i++)connectors[i].setContainer(this.container);}if (started && (oldContainer != null) &&(oldContainer instanceof Lifecycle)) {try {((Lifecycle) oldContainer).stop();} catch (LifecycleException e) {;}}// Report this property change to interested listenerssupport.firePropertyChange("container", oldContainer, this.container);} 對以上代碼的補充描述(description):與服務組件相關聯的servlet容器的實例將被傳給每個連接器對象的 setContainer()方法,這樣在服務組件中就可以形成每個連接器和 servlet容器間的關聯關系; 5)addConnector()方法和removeConnector()方法:前者將連接器添加到服務組件中, 后者將某個連接器移除; (補充:addConnector()方法會初始化并啟動添加到其中的連接器,參見下面的start()方法——highlight line.) public void addConnector(Connector connector) { //org.apache.catalina.core.StandardService.addConnector().synchronized (connectors) {connector.setContainer(this.container);connector.setService(this);Connector results[] = new Connector[connectors.length + 1];System.arraycopy(connectors, 0, results, 0, connectors.length);results[connectors.length] = connector;connectors = results;if (initialized) {try {connector.initialize(); //highlight line.} catch (LifecycleException e) {e.printStackTrace(System.err);}}if (started && (connector instanceof Lifecycle)) {try {((Lifecycle) connector).start();//highlight line.} catch (LifecycleException e) {;}}// Report this property change to interested listenerssupport.firePropertyChange("connector", null, connector);}} public void removeConnector(Connector connector) { //org.apache.catalina.core.StandardService.removeConnector().synchronized (connectors) {int j = -1;for (int i = 0; i < connectors.length; i++) {if (connector == connectors[i]) {j = i;break;}}if (j < 0)return;if (started && (connectors[j] instanceof Lifecycle)) {try {((Lifecycle) connectors[j]).stop();} catch (LifecycleException e) {;}}connectors[j].setContainer(null);connector.setService(null);int k = 0;Connector results[] = new Connector[connectors.length - 1];for (int i = 0; i < connectors.length; i++) {if (i != j)results[k++] = connectors[i];}connectors = results;// Report this property change to interested listenerssupport.firePropertyChange("connector", connector, null);}} 【4.2】與生命周期有關的方法(start() + stop() + initialize()) 1)intro:?與生命周期有關的方法包括Lifecycle.start() +?Lifecycle.stop()+ initialize()方法;其中initialize()方法會調用該服務組件中所有連接器的 initialize()方法; 2)org.apache.catalina.core.StandardService.initialize()源碼如下: public void initialize() throws LifecycleException { //org.apache.catalina.core.StandardService.initialize().if (initialized) throw new LifecycleException (sm.getString("standardService.initialize.initialized"));initialized = true;// Initialize our defined Connectorssynchronized (connectors) {for (int i = 0; i < connectors.length; i++) {connectors[i].initialize(); //highlight line.}}} public void initialize() throws LifecycleException { //org.apache.catalina.connector.HttpConnector.initialize().if (initialized)throw new LifecycleException (sm.getString("httpConnector.alreadyInitialized"));this.initialized=true;Exception eRethrow = null;// Establish a server socket on the specified porttry {serverSocket = open();} catch (IOException ioe) {log("httpConnector, io problem: ", ioe);eRethrow = ioe;} catch (KeyStoreException kse) {log("httpConnector, keystore problem: ", kse);eRethrow = kse;} catch (NoSuchAlgorithmException nsae) {log("httpConnector, keystore algorithm problem: ", nsae);eRethrow = nsae;} catch (CertificateException ce) {log("httpConnector, certificate problem: ", ce);eRethrow = ce;} catch (UnrecoverableKeyException uke) {log("httpConnector, unrecoverable key: ", uke);eRethrow = uke;} catch (KeyManagementException kme) {log("httpConnector, key management problem: ", kme);eRethrow = kme;}if ( eRethrow != null )throw new LifecycleException(threadName + ".open", eRethrow);} 3)org.apache.catalina.core.StandardService.start()源碼如下:該方法負責啟動被添加到 該服務組件中的連接器和servlet容器; public void start() throws LifecycleException { // org.apache.catalina.core.StandardService.start()// Validate and update our current component stateif (started) {throw new LifecycleException(sm.getString("standardService.start.started"));}// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);System.out.println(sm.getString("standardService.start.name", this.name));lifecycle.fireLifecycleEvent(START_EVENT, null);started = true;// Start our defined Container firstif (container != null) {synchronized (container) {if (container instanceof Lifecycle) {((Lifecycle) container).start(); //highlight line.}}}// Start our defined Connectors secondsynchronized (connectors) {for (int i = 0; i < connectors.length; i++) {if (connectors[i] instanceof Lifecycle)((Lifecycle) connectors[i]).start(); //highlight line.}}// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);} 4)org.apache.catalina.core.StandardService.stop()源碼如下:用于關閉與該服務組件相關聯的servlet容器和所有連接器;
public void stop() throws LifecycleException { // org.apache.catalina.core.StandardService.stop()// Validate and update our current component stateif (!started) {throw new LifecycleException(sm.getString("standardService.stop.notStarted"));}// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);lifecycle.fireLifecycleEvent(STOP_EVENT, null);System.out.println(sm.getString("standardService.stop.name", this.name));started = false;// Stop our defined Connectors firstsynchronized (connectors) {for (int i = 0; i < connectors.length; i++) {if (connectors[i] instanceof Lifecycle)((Lifecycle) connectors[i]).stop();//highlight line.}}// Stop our defined Container secondif (container != null) {synchronized (container) {if (container instanceof Lifecycle) {((Lifecycle) container).stop(); //highlight line.}}}// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);} 【5】應用程序? 1)intro:本應用程序旨在說明如何使用服務器組件和服務組件,特別是在 StandardServer類中如何利用 啟動和關閉機制; 2)共有3個類:Bootstrap類+Stopper類+SimpleContextConfig類(它其實是作為 LifecycleListener的實例) 2.1)Bootstrap類源碼 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);}}} } 對以上代碼的調用過程分析(steps): step1)該方法會創建一個連接器,兩個Wrapper實例,一個Context容器,一個Host容器和一個Engine容器; step2)將兩個Wrapper 實例添加到 Context容器中, 將Context容器添加到 Host容器中,再將Host容器添加到Engine容器中;(干貨——到此,tomcat的4種容器創建完畢) step3)但是,這并沒有將連接器和最頂層的容器Engine相關聯; step4)相反,main()方法創建了一個Service對象,設置其名稱,再創建一個 Server對象,將Service實例添加到 Server實例中: Service service = new StandardService();service.setName("Stand-alone Service");Server server = new StandardServer();server.addService(service); step5)main()方法將連接器和Engine容器添加到 Service實例中; service.addConnector(connector);service.setContainer(engine); step6)main()方法調用 Server.intialize() 和 Server.start()方法,初始化連接器,并啟動連接器和servlet容器; step7)接下來,main()方法調用Server.await()方法,進入循環等待,監聽8085端口等待關閉命令;(干貨——參見上述【2.4】await()方法) Attention)此時連接器已經處于運行狀態,等待另一個端口8080上的 http 請求;(監聽8085和8080端口分別處于不同的線程) // Start the new serverif (server instanceof Lifecycle) {try {server.initialize(); // step6((Lifecycle) server).start(); // step6server.await(); // step7// the program waits until the await method returns,// i.e. until a shutdown command is received.}catch (LifecycleException e) {e.printStackTrace(System.out);}} step8)除非收到了正確的關閉命令,否則await()方法是不會返回的。當 await()方法返回時,main()方法調用 Server.stop()方法,實際上關閉其所有組件; // Shut down the serverif (server instanceof Lifecycle) {try {((Lifecycle) server).stop();}catch (LifecycleException e) {e.printStackTrace(System.out);}} 2.2)Stopper類 源碼 2.2.1)intro:Stopper類提供了更加優雅的方式來關閉Catalina服務器,也保證了所有的生命周期組件中的stop() 方法都能夠調用;
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.");}} } 對以上代碼的分析(Analysis): Stopper類的main()方法會創建一個 Socket對象,然后將正確 的關閉命令 “SHOTDOWN”字符串發送到 端口8085。Catalina 服務器在接收到關閉命令后,就會執行相應的關閉操作;
Attention)本文還是習慣性地總結了上述測試用例的調用流程圖
 
 
 
 
A1)HttpConnector.start()方法的調用過程如下(下面的圖借用自 tomcat(11) 中章節【1】的4.1小節的圖):
 
 
A2)StandardsEngine.start()方法的調用過程如下(下面的第二張圖借用自tomcat(13)中的章節【10】 ,兩張圖可以結合起來看):
 
 
(補充,需要了解?HttpConnector.initialize() 和 start() 方法 以及 StandardContext.start()方法的具體調用過程,參見?tomcat(supplement)HttpConnector.initialize() 和 start() 方法 以及 StandardContext.start()方法的分析)
 
 
【6】執行結果 1)console info E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>java -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;lib/catalina-5.5.4.jar;lib/naming-common. jar;lib/commons-collections.jar;lib/naming-resources.jar;lib/commons-digester.jar;lib/catalina.jar;E:\bench-cluster\cloud-data-preprocess\HowTomcatWok s\weroot com.tomcat.chapter14.startup.Bootstrap HttpConnector Opening server socket on all host IP addresses Starting service Stand-alone Service Apache Tomcat/4.1.24 WebappLoader[/app1]: Deploying class repositories to work directory E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\work\_\localhost\app1 WebappLoader[/app1]: Deploy class files /WEB-INF/classes to E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\webapps\app1\WEB-INF\classes StandardManager[/app1]: Seeding random number generator class java.security.SecureRandom StandardManager[/app1]: Seeding of random number generator has been completed StandardManager[/app1]: IOException while loading persisted sessions: java.io.EOFException java.io.EOFExceptionat java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)at java.io.ObjectInputStream.readStreamHeader(Unknown Source)at java.io.ObjectInputStream.<init>(Unknown Source)at org.apache.catalina.util.CustomObjectInputStream.<init>(CustomObjectInputStream.java:103)at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1188)at org.apache.catalina.core.StandardHost.start(StandardHost.java:738)at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1188)at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:347)at org.apache.catalina.core.StandardService.start(StandardService.java:497)at org.apache.catalina.core.StandardServer.start(StandardServer.java:2190)at com.tomcat.chapter14.startup.Bootstrap.main(Bootstrap.java:77) StandardManager[/app1]: Exception loading sessions from persistent storage java.io.EOFExceptionat java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)at java.io.ObjectInputStream.readStreamHeader(Unknown Source)at java.io.ObjectInputStream.<init>(Unknown Source)at org.apache.catalina.util.CustomObjectInputStream.<init>(CustomObjectInputStream.java:103)at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1188)at org.apache.catalina.core.StandardHost.start(StandardHost.java:738)at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1188)at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:347)at org.apache.catalina.core.StandardService.start(StandardService.java:497)at org.apache.catalina.core.StandardServer.start(StandardServer.java:2190)at com.tomcat.chapter14.startup.Bootstrap.main(Bootstrap.java:77) HttpConnector[8080] Starting background thread ModernServlet -- init StandardHost[localhost]: MAPPING configuration error for request URI /favicon.ico 2)執行效果
 
 
 
 
 
 
                            
                        
                        
                        【1】服務器組件(org.apache.catalina.Server) 1)org.apache.catalina.Server接口的實例表示 Catalina的整個servlet引擎,囊括了所有的組件。服務器組件使用了一種優雅的方法來啟動/關閉 整個系統,不需要再對連接器和容器分別啟動/ 關閉; 2)啟動/關閉機制的工作原理:當啟動服務器組件時,它會啟動其中所有的組件,然后它就無限期地等待關閉命令。如果想要關閉系統,可以向指定端口發送一條關閉命令。服務器組件接收到關閉命令后,就會關閉其中所有的組件; 3)服務組件:服務器組件引入了服務組件來包含其他組件,如一個容器組件和一個或多個連接器組件;(干貨——注意區分服務器組件和服務組件,以下代碼摘自某測試用例,StandardService表服務組件,而StandardServer表服務器組件) Service service = new StandardService();service.setName("Stand-alone Service");Server server = new StandardServer();server.addService(service);
4)服務器組件Server接口定義 package org.apache.catalina; import org.apache.catalina.deploy.NamingResources; public interface Server { public String getInfo();public NamingResources getGlobalNamingResources(); public void setGlobalNamingResources (NamingResources globalNamingResources); public int getPort(); public void setPort(int port); public String getShutdown(); public void setShutdown(String shutdown); public void addService(Service service); public void await(); public Service findService(String name); public Service[] findServices(); public void removeService(Service service);public void initialize() throws LifecycleException; } 對以上代碼的分析(Analysis): A1)shutdown屬性:保存了必須發送給Server 實例用來關閉整個系統的關閉命令; A2)port屬性:定義了服務器組件會從哪個端口獲取關閉命令; A3)addService() 和 removeService()方法:為服務器組件添加或移除服務組件; A4)findService()方法:返回添加到該服務器組件中的所有服務組件; A5)initialize()方法:包含在系統啟動前要執行的一些代碼;
【2】StandardServer類 1)intro:一個服務器組件可以有0個或多個服務組件;(干貨——一個服務器組件可以有0個或多個服務組件) 2)StandardServer類有4個與生命周期相關的方法,分別是initialize()方法,start()方法,stop()方法 和 await()方法;
【2.1】initialize()方法 1)intro:用于初始化添加到其中的服務器組件; public void initialize() throws LifecycleException { //org.apache.catalina.StandardServer.initialize().if (initialized)throw new LifecycleException (sm.getString("standardServer.initialize.initialized"));initialized = true;// Initialize our defined Servicesfor (int i = 0; i < services.length; i++) {services[i].initialize();}} 【2.2】start() 方法 1)intro:該方法用于啟動服務器組件,即啟動所有的服務組件,逐個啟動所有組件,如連接器組件和servlet容器; public void start() throws LifecycleException { //org.apache.catalina.StandardServer.start().// Validate and update our current component stateif (started)throw new LifecycleException(sm.getString("standardServer.start.started"));// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);lifecycle.fireLifecycleEvent(START_EVENT, null);started = true;// Start our defined Servicessynchronized (services) {for (int i = 0; i < services.length; i++) {if (services[i] instanceof Lifecycle)((Lifecycle) services[i]).start();}}// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);} 【2.3】stop()方法 1)intro:用于關閉服務器組件;(調用stop方法會關閉所有的服務組件,并reset started,這樣,才能再次啟動服務器組件;參見start()方法) public void stop() throws LifecycleException { //org.apache.catalina.StandardServer.stop().// Validate and update our current component stateif (!started)throw new LifecycleException(sm.getString("standardServer.stop.notStarted"));// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);lifecycle.fireLifecycleEvent(STOP_EVENT, null);started = false;// Stop our defined Servicesfor (int i = 0; i < services.length; i++) {if (services[i] instanceof Lifecycle)((Lifecycle) services[i]).stop();}// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);} 【2.4】await()方法 1)intro:該方法負責等待關閉整個tomcat 部署的命令; 2)await()方法:調用該方法后,會一直阻塞,直到它從 8085 端口(也可以是其他端口)上接收到關閉命令(accept()方法);當await()方法返回時,會運行stop方法來關閉其下的所有子組件; public void await() { //org.apache.catalina.StandardServer.await().// Set up a server socket to wait onServerSocket 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);}// Loop waiting for a connection and a valid commandwhile (true) {// Wait for the next connectionSocket socket = null;InputStream stream = null;try {socket = serverSocket.accept(); //highlight line.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--;}// Close the socket now that we are done with ittry {socket.close();} catch (IOException e) {;}// Match against our command stringboolean match = command.toString().equals(shutdown); //highlight line.if (match) {break;} elseSystem.err.println("StandardServer.await: Invalid command '" +command.toString() + "' received");}// Close the server socket and returntry {serverSocket.close();} catch (IOException e) {;}} 對以上代碼的調用過程分析(steps): step1)創建一個ServerSocket對象,監聽8085端口,并在while循環中調用它的accept()方法; step2)當在指定端口上接收到消息時,accept()方法才會返回; step3)將接收到的消息與關閉命令的字符串相比較,相同的話就跳出while循環,關閉 ServerSocket,否則會再次循環,繼續等待消息;
【3】Service接口(服務組件接口——org.apache.catalina.core.Service) 1)intro:一個服務組件可以有一個servlet容器 和 多個連接器實例;(干貨——一個服務器組件可以有0個或多個服務組件,而一個服務組件可以有一個servlet容器 和 多個連接器實例) 2)可以自由地把連接器添加到服務組件中,所有的連接器都會與這個servlet容器相關聯;? package org.apache.catalina; public interface Service { public Container getContainer(); public void setContainer(Container container); public String getInfo(); public String getName(); public void setName(String name); public Server getServer(); public void setServer(Server server); public void addConnector(Connector connector); public Connector[] findConnectors(); public void removeConnector(Connector connector); public void initialize() throws LifecycleException; } 【4】StandardService類(org.apache.catalina.core.StandardService) 1)StandardService.initialize()方法:用于初始化添加到其中的所有連接器; 2)StandardService.start()方法:啟動連接器和所有servlet容器;
【4.1】connectors 和 container 1)StandardService實例中有兩種組件:連接器和servlet容器;其中servlet容器只有一個,而連接器則可以有多個;(干貨——StandardService實例中有兩種組件:連接器和servlet容器) 2)多個連接器使tomcat 可以為多種不同的請求協議提供服務;如,一個連接器處理HTTP請求,而另一個連接器處理HTTPS請求;(干貨——多個連接器使tomcat 可以為多種不同的請求協議提供服務) 3)變量connectors 和 container:StandardService類使用 container 變量來指向一個Container接口的實例,而數組connectors 來保存所有連接器的引用;(干貨——變量connectors 和 container的用途) private Connector connectors[] = new Connector[0]; // defined in StandardService. 4)setContainer()方法:需要調用setContainer()方法 將servlet容器與服務組件相關聯; public void setContainer(Container container) { // org.apache.catalina.core.StandardService.setContainer().Container oldContainer = this.container;if ((oldContainer != null) && (oldContainer instanceof Engine))((Engine) oldContainer).setService(null);this.container = container;if ((this.container != null) && (this.container instanceof Engine))((Engine) this.container).setService(this);if (started && (this.container != null) &&(this.container instanceof Lifecycle)) {try {((Lifecycle) this.container).start();} catch (LifecycleException e) {;}}synchronized (connectors) {for (int i = 0; i < connectors.length; i++)connectors[i].setContainer(this.container);}if (started && (oldContainer != null) &&(oldContainer instanceof Lifecycle)) {try {((Lifecycle) oldContainer).stop();} catch (LifecycleException e) {;}}// Report this property change to interested listenerssupport.firePropertyChange("container", oldContainer, this.container);} 對以上代碼的補充描述(description):與服務組件相關聯的servlet容器的實例將被傳給每個連接器對象的 setContainer()方法,這樣在服務組件中就可以形成每個連接器和 servlet容器間的關聯關系; 5)addConnector()方法和removeConnector()方法:前者將連接器添加到服務組件中, 后者將某個連接器移除; (補充:addConnector()方法會初始化并啟動添加到其中的連接器,參見下面的start()方法——highlight line.) public void addConnector(Connector connector) { //org.apache.catalina.core.StandardService.addConnector().synchronized (connectors) {connector.setContainer(this.container);connector.setService(this);Connector results[] = new Connector[connectors.length + 1];System.arraycopy(connectors, 0, results, 0, connectors.length);results[connectors.length] = connector;connectors = results;if (initialized) {try {connector.initialize(); //highlight line.} catch (LifecycleException e) {e.printStackTrace(System.err);}}if (started && (connector instanceof Lifecycle)) {try {((Lifecycle) connector).start();//highlight line.} catch (LifecycleException e) {;}}// Report this property change to interested listenerssupport.firePropertyChange("connector", null, connector);}} public void removeConnector(Connector connector) { //org.apache.catalina.core.StandardService.removeConnector().synchronized (connectors) {int j = -1;for (int i = 0; i < connectors.length; i++) {if (connector == connectors[i]) {j = i;break;}}if (j < 0)return;if (started && (connectors[j] instanceof Lifecycle)) {try {((Lifecycle) connectors[j]).stop();} catch (LifecycleException e) {;}}connectors[j].setContainer(null);connector.setService(null);int k = 0;Connector results[] = new Connector[connectors.length - 1];for (int i = 0; i < connectors.length; i++) {if (i != j)results[k++] = connectors[i];}connectors = results;// Report this property change to interested listenerssupport.firePropertyChange("connector", connector, null);}} 【4.2】與生命周期有關的方法(start() + stop() + initialize()) 1)intro:?與生命周期有關的方法包括Lifecycle.start() +?Lifecycle.stop()+ initialize()方法;其中initialize()方法會調用該服務組件中所有連接器的 initialize()方法; 2)org.apache.catalina.core.StandardService.initialize()源碼如下: public void initialize() throws LifecycleException { //org.apache.catalina.core.StandardService.initialize().if (initialized) throw new LifecycleException (sm.getString("standardService.initialize.initialized"));initialized = true;// Initialize our defined Connectorssynchronized (connectors) {for (int i = 0; i < connectors.length; i++) {connectors[i].initialize(); //highlight line.}}} public void initialize() throws LifecycleException { //org.apache.catalina.connector.HttpConnector.initialize().if (initialized)throw new LifecycleException (sm.getString("httpConnector.alreadyInitialized"));this.initialized=true;Exception eRethrow = null;// Establish a server socket on the specified porttry {serverSocket = open();} catch (IOException ioe) {log("httpConnector, io problem: ", ioe);eRethrow = ioe;} catch (KeyStoreException kse) {log("httpConnector, keystore problem: ", kse);eRethrow = kse;} catch (NoSuchAlgorithmException nsae) {log("httpConnector, keystore algorithm problem: ", nsae);eRethrow = nsae;} catch (CertificateException ce) {log("httpConnector, certificate problem: ", ce);eRethrow = ce;} catch (UnrecoverableKeyException uke) {log("httpConnector, unrecoverable key: ", uke);eRethrow = uke;} catch (KeyManagementException kme) {log("httpConnector, key management problem: ", kme);eRethrow = kme;}if ( eRethrow != null )throw new LifecycleException(threadName + ".open", eRethrow);} 3)org.apache.catalina.core.StandardService.start()源碼如下:該方法負責啟動被添加到 該服務組件中的連接器和servlet容器; public void start() throws LifecycleException { // org.apache.catalina.core.StandardService.start()// Validate and update our current component stateif (started) {throw new LifecycleException(sm.getString("standardService.start.started"));}// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);System.out.println(sm.getString("standardService.start.name", this.name));lifecycle.fireLifecycleEvent(START_EVENT, null);started = true;// Start our defined Container firstif (container != null) {synchronized (container) {if (container instanceof Lifecycle) {((Lifecycle) container).start(); //highlight line.}}}// Start our defined Connectors secondsynchronized (connectors) {for (int i = 0; i < connectors.length; i++) {if (connectors[i] instanceof Lifecycle)((Lifecycle) connectors[i]).start(); //highlight line.}}// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);} 4)org.apache.catalina.core.StandardService.stop()源碼如下:用于關閉與該服務組件相關聯的servlet容器和所有連接器;
public void stop() throws LifecycleException { // org.apache.catalina.core.StandardService.stop()// Validate and update our current component stateif (!started) {throw new LifecycleException(sm.getString("standardService.stop.notStarted"));}// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);lifecycle.fireLifecycleEvent(STOP_EVENT, null);System.out.println(sm.getString("standardService.stop.name", this.name));started = false;// Stop our defined Connectors firstsynchronized (connectors) {for (int i = 0; i < connectors.length; i++) {if (connectors[i] instanceof Lifecycle)((Lifecycle) connectors[i]).stop();//highlight line.}}// Stop our defined Container secondif (container != null) {synchronized (container) {if (container instanceof Lifecycle) {((Lifecycle) container).stop(); //highlight line.}}}// Notify our interested LifecycleListenerslifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);} 【5】應用程序? 1)intro:本應用程序旨在說明如何使用服務器組件和服務組件,特別是在 StandardServer類中如何利用 啟動和關閉機制; 2)共有3個類:Bootstrap類+Stopper類+SimpleContextConfig類(它其實是作為 LifecycleListener的實例) 2.1)Bootstrap類源碼 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);}}} } 對以上代碼的調用過程分析(steps): step1)該方法會創建一個連接器,兩個Wrapper實例,一個Context容器,一個Host容器和一個Engine容器; step2)將兩個Wrapper 實例添加到 Context容器中, 將Context容器添加到 Host容器中,再將Host容器添加到Engine容器中;(干貨——到此,tomcat的4種容器創建完畢) step3)但是,這并沒有將連接器和最頂層的容器Engine相關聯; step4)相反,main()方法創建了一個Service對象,設置其名稱,再創建一個 Server對象,將Service實例添加到 Server實例中: Service service = new StandardService();service.setName("Stand-alone Service");Server server = new StandardServer();server.addService(service); step5)main()方法將連接器和Engine容器添加到 Service實例中; service.addConnector(connector);service.setContainer(engine); step6)main()方法調用 Server.intialize() 和 Server.start()方法,初始化連接器,并啟動連接器和servlet容器; step7)接下來,main()方法調用Server.await()方法,進入循環等待,監聽8085端口等待關閉命令;(干貨——參見上述【2.4】await()方法) Attention)此時連接器已經處于運行狀態,等待另一個端口8080上的 http 請求;(監聽8085和8080端口分別處于不同的線程) // Start the new serverif (server instanceof Lifecycle) {try {server.initialize(); // step6((Lifecycle) server).start(); // step6server.await(); // step7// the program waits until the await method returns,// i.e. until a shutdown command is received.}catch (LifecycleException e) {e.printStackTrace(System.out);}} step8)除非收到了正確的關閉命令,否則await()方法是不會返回的。當 await()方法返回時,main()方法調用 Server.stop()方法,實際上關閉其所有組件; // Shut down the serverif (server instanceof Lifecycle) {try {((Lifecycle) server).stop();}catch (LifecycleException e) {e.printStackTrace(System.out);}} 2.2)Stopper類 源碼 2.2.1)intro:Stopper類提供了更加優雅的方式來關閉Catalina服務器,也保證了所有的生命周期組件中的stop() 方法都能夠調用;
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.");}} } 對以上代碼的分析(Analysis): Stopper類的main()方法會創建一個 Socket對象,然后將正確 的關閉命令 “SHOTDOWN”字符串發送到 端口8085。Catalina 服務器在接收到關閉命令后,就會執行相應的關閉操作;
Attention)本文還是習慣性地總結了上述測試用例的調用流程圖
A1)HttpConnector.start()方法的調用過程如下(下面的圖借用自 tomcat(11) 中章節【1】的4.1小節的圖):
A2)StandardsEngine.start()方法的調用過程如下(下面的第二張圖借用自tomcat(13)中的章節【10】 ,兩張圖可以結合起來看):
(補充,需要了解?HttpConnector.initialize() 和 start() 方法 以及 StandardContext.start()方法的具體調用過程,參見?tomcat(supplement)HttpConnector.initialize() 和 start() 方法 以及 StandardContext.start()方法的分析)
【6】執行結果 1)console info E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>java -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;lib/catalina-5.5.4.jar;lib/naming-common. jar;lib/commons-collections.jar;lib/naming-resources.jar;lib/commons-digester.jar;lib/catalina.jar;E:\bench-cluster\cloud-data-preprocess\HowTomcatWok s\weroot com.tomcat.chapter14.startup.Bootstrap HttpConnector Opening server socket on all host IP addresses Starting service Stand-alone Service Apache Tomcat/4.1.24 WebappLoader[/app1]: Deploying class repositories to work directory E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\work\_\localhost\app1 WebappLoader[/app1]: Deploy class files /WEB-INF/classes to E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\webapps\app1\WEB-INF\classes StandardManager[/app1]: Seeding random number generator class java.security.SecureRandom StandardManager[/app1]: Seeding of random number generator has been completed StandardManager[/app1]: IOException while loading persisted sessions: java.io.EOFException java.io.EOFExceptionat java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)at java.io.ObjectInputStream.readStreamHeader(Unknown Source)at java.io.ObjectInputStream.<init>(Unknown Source)at org.apache.catalina.util.CustomObjectInputStream.<init>(CustomObjectInputStream.java:103)at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1188)at org.apache.catalina.core.StandardHost.start(StandardHost.java:738)at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1188)at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:347)at org.apache.catalina.core.StandardService.start(StandardService.java:497)at org.apache.catalina.core.StandardServer.start(StandardServer.java:2190)at com.tomcat.chapter14.startup.Bootstrap.main(Bootstrap.java:77) StandardManager[/app1]: Exception loading sessions from persistent storage java.io.EOFExceptionat java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)at java.io.ObjectInputStream.readStreamHeader(Unknown Source)at java.io.ObjectInputStream.<init>(Unknown Source)at org.apache.catalina.util.CustomObjectInputStream.<init>(CustomObjectInputStream.java:103)at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1188)at org.apache.catalina.core.StandardHost.start(StandardHost.java:738)at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1188)at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:347)at org.apache.catalina.core.StandardService.start(StandardService.java:497)at org.apache.catalina.core.StandardServer.start(StandardServer.java:2190)at com.tomcat.chapter14.startup.Bootstrap.main(Bootstrap.java:77) HttpConnector[8080] Starting background thread ModernServlet -- init StandardHost[localhost]: MAPPING configuration error for request URI /favicon.ico 2)執行效果
總結
以上是生活随笔為你收集整理的tomcat(14)服务器组件和服务组件的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 选多大电池更划算电池大小怎么选
- 下一篇: 工业平板电脑哪些场景要带电池?那些场景不
