Tomcat启动过程源码分析六
前言
上一篇文章中我們討論了Catalina類中start方法中一部分,今天這篇文章我們把Catalina類的start方法剩余部分講解完畢,在講解代碼之前我們先看之前的一篇關于ShutdownHook的文章,有利于后面代碼的講解。
/*** Start a new server instance.*/public void start() {if (getServer() == null) {load();}if (getServer() == null) {log.fatal("Cannot start server. Server instance is not configured.");return;}long t1 = System.nanoTime();// Start the new servertry {getServer().start();} catch (LifecycleException e) {log.fatal(sm.getString("catalina.serverStartFail"), e);try {getServer().destroy();} catch (LifecycleException e1) {log.debug("destroy() failed for failed Server ", e1);}return;}long t2 = System.nanoTime();if(log.isInfoEnabled()) {log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");}//剩余部分 從這里開始// Register shutdown hook//1111111111111if (useShutdownHook) {if (shutdownHook == null) {shutdownHook = new CatalinaShutdownHook();}Runtime.getRuntime().addShutdownHook(shutdownHook);// If JULI is being used, disable JULI's shutdown hook since// shutdown hooks run in parallel and log messages may be lost// if JULI's hook completes before the CatalinaShutdownHook()LogManager logManager = LogManager.getLogManager();if (logManager instanceof ClassLoaderLogManager) {((ClassLoaderLogManager) logManager).setUseShutdownHook(false);}}//2222222if (await) {await();stop();}}在start方法的末尾可以看到,tomcat注冊一個鉤子,我們來具體鉤子的代碼。
// XXX Should be moved to embedded ! /*** Shutdown hook which will perform a clean shutdown of Catalina if needed.*/ protected class CatalinaShutdownHook extends Thread {@Overridepublic void run() {try {if (getServer() != null) {Catalina.this.stop();}} catch (Throwable ex) {ExceptionUtils.handleThrowable(ex);log.error(sm.getString("catalina.shutdownHookFail"), ex);} finally {// If JULI is used, shut JULI down *after* the server shuts down// so log messages aren't lostLogManager logManager = LogManager.getLogManager();if (logManager instanceof ClassLoaderLogManager) {((ClassLoaderLogManager) logManager).shutdown();}}} }可以看到鉤子主要的代碼是這行
Catalina.this.stop()因為CatalinaShutdownHook是Catalina類的內部類,所以這句代碼就是指向了外部類對象并且調用了stop方法,也就是查看Catalina類的stop方法。
/*** Stop an existing server instance.*/ public void stop() {try {// Remove the ShutdownHook first so that server.stop()// doesn't get invoked twice//111111111if (useShutdownHook) {Runtime.getRuntime().removeShutdownHook(shutdownHook);// If JULI is being used, re-enable JULI's shutdown to ensure// log messages are not lostLogManager logManager = LogManager.getLogManager();if (logManager instanceof ClassLoaderLogManager) {((ClassLoaderLogManager) logManager).setUseShutdownHook(true);}}} catch (Throwable t) {ExceptionUtils.handleThrowable(t);// This will fail on JDK 1.2. Ignoring, as Tomcat can run// fine without the shutdown hook.}// Shut down the servertry {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 {//22222s.stop();s.destroy();}} catch (LifecycleException e) {log.error("Catalina.stop", e);}}其實這段代碼就是關閉tomcat的代碼,我們來分析下stop方法都做了什么。
在stop方法中1處是移除了鉤子,鉤子執行的條件又是在異常關閉或者主線程執行完畢,
- 所以假如tomcat異常關閉,那么調用一次StandardServer.stop()方法(在鉤子代碼2處調用一次)
- 如果tomcat是正常關閉的(調用stop.bat/sh)那么會調用兩次(第一次是在start方法的數字2處調用,第二次是主線程執行代碼完畢以后鉤子代碼內部數字2處又會調用一次)
所以在鉤子代碼一開始的地方先移除鉤子,這樣可以兼容無論是正常關閉還是異常關閉都只會調用一次StandardServer.stop()。
在鉤子代碼2的地方先是獲取了運行的tomcat的實例server對象,對應實現類StandardServer,然后判斷了如果tomcat處于需要關閉的狀態則先調用stop方法,再調用destroy方法,我們先來查看StandardServer對象的stop方法。
@Override protected void stopInternal() throws LifecycleException {setState(LifecycleState.STOPPING);fireLifecycleEvent(CONFIGURE_STOP_EVENT, null);// Stop our defined Servicesfor (int i = 0; i < services.length; i++) {//調用StandardService.stopservices[i].stop();}globalNamingResources.stop();stopAwait(); }在StandardServer對象內部沒有找到stop方法,但是有stopInternal方法,可以看出依舊使用了模版設計模式,我們簡單看下stopInternal方法,類似init,start方法,這里遍歷了所有的service調用了stop方法,我們繼續查看StandardService的stop方法。
@Override protected void stopInternal() throws LifecycleException {// Pause connectors firstsynchronized (connectorsLock) {for (Connector connector: connectors) {try {//調用connector的pause方法,目的是在關閉的時候拒絕外部的請求connector.pause();} catch (Exception e) {log.error(sm.getString("standardService.connector.pauseFailed",connector), e);}}}if(log.isInfoEnabled())log.info(sm.getString("standardService.stop.name", this.name));setState(LifecycleState.STOPPING);// Stop our defined Container secondif (container != null) {synchronized (container) {//關閉container,container又會調用內部組件的stop方法來依次關閉所有組件container.stop();}}// Now stop the connectorssynchronized (connectorsLock) {for (Connector connector: connectors) {if (!LifecycleState.STARTED.equals(connector.getState())) {// Connectors only need stopping if they are currently// started. They may have failed to start or may have been// stopped (e.g. via a JMX call)continue;}try {//停止connector 停止對外的服務connector.stop();} catch (Exception e) {log.error(sm.getString("standardService.connector.stopFailed",connector), e);}}}synchronized (executors) {for (Executor executor: executors) {executor.stop();}} }可以看到代碼的形式非常類似之前文章提到的init,start方法,我們只瀏覽到這部分,下面的讀者可以參照之前查看start,init方法的形式自行往下查看源碼。到這里StandardServer的stop方法我們就看完了,但是在鉤子代碼里還調用了StandardServer的destroy方法,我們繼續查看下StandardServer的destroy方法。
@Override protected void destroyInternal() throws LifecycleException {// Destroy our defined Servicesfor (int i = 0; i < services.length; i++) {services[i].destroy();}globalNamingResources.destroy();unregister(onameMBeanFactory);unregister(onameStringCache);super.destroyInternal(); }看到這里大概大家又很熟悉了,形式跟init,start,stop很類似,也是調用了StandardService的destroy方法,我們就不繼續查看了,留給讀者自行查看,那么我們鉤子方法就看完了??梢钥闯鋈绻鹴omcat出現了異常關閉,那么最終是調用的Catalina的stop方法,而Catalina的stop方法又調用StandardServer的stop和destroy方法,我們繼續往下看start方法的最后一部分。
//2222222 if (await) {await();stop(); }await屬性在Bootstrap類的main方法中被設置為true,查看wait方法:
/*** Await and shutdown.*/ public void await() {getServer().await();}調用StandardServer的await方法,由于代碼很多,省略了部分,只展示了部分重要代碼
/*** Wait until a proper shutdown command is received, then return.* This keeps the main thread alive - the thread pool listening for http * connections is daemon threads.*/ @Override public void await() {// Negative values - don't wait on port - tomcat is embedded or we just don't like ports//簡單的錯誤檢查 省略...// Set up a server socket to wait ontry {awaitSocket = new ServerSocket(port, 1,InetAddress.getByName(address));} catch (IOException e) {//....}try {awaitThread = Thread.currentThread();// Loop waiting for a connection and a valid commandwhile (!stopAwait) {//1111ServerSocket serverSocket = awaitSocket;if (serverSocket == null) {break;}// Wait for the next connectionSocket socket = null;StringBuilder command = new StringBuilder();try {InputStream stream;long acceptStartTime = System.currentTimeMillis();try {socket = serverSocket.accept();socket.setSoTimeout(10 * 1000); // Ten secondsstream = socket.getInputStream();} catch (SocketTimeoutException ste) {//}//省略部分} finally {// Close the socket now that we are done with ittry {if (socket != null) {socket.close();}} catch (IOException e) {// Ignore}}//22222222222// Read a set of characters from the socketint expected = 1024; // Cut off to avoid DoS attackwhile (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;}if (ch < 32) // Control character or EOF terminates loopbreak;command.append((char) ch);expected--;} // Match against our command string//333333333333boolean match = command.toString().equals(shutdown);if (match) {log.info(sm.getString("standardServer.shutdownViaPort"));break;} elselog.warn("StandardServer.await: Invalid command '"+ command.toString() + "' received");}} finally {} }private volatile boolean stopAwait = false;可以看到await方法中有個while循環,循環中主要做了三件事,在代碼1的部分先新建了一個socket,所使用的ip,port分別對應本地和8005,其實這個端口可以更改對應的就是server.xml中Server標簽的port,新建了socket以后,調用其accept方法等待,也就是說如果在對應ip,port上有請求進來就會被這里接收到。我們繼續看代碼2,在第二步拿到accept到的線程的輸入流,從輸入流中讀取拼裝字符串,第三步把拼裝好的字符串和變量shutdown比對,如果相同就跳出循環。我們查看shutdown指向的是一個SHUTDOWN字符串??吹竭@里有點不明所以,但是await方法就這樣結束了,方法的作用我們暫時不說先繼續往下看,查看Catalina類start方法的最后一點stop方法,點開stop方法發現我們在看shutdownhook代碼的時候已經看過了,這個方法就是關閉tomcat所有的組件,包括停止和銷毀兩個步驟。
看到這里就比較明朗了,在Tomcat啟動的末尾,也就是所有組件已經start完畢以后,tomcat內部新建了一個socket用來接收在指定ip指定端口的請求,也就是在server.xml中配置的關閉端口,然后這個socket就會一直等待請求進入(accpet),如果有請求進入了,并且攜帶的命令是SHUTDOWN(這個也是在server.xml中配置),那么就調用stop方法,也就意味著關閉這個Tomcat,所以Tomcat的關閉就是在指定ip,port上發送一個SHUTDOWN命令即可。
下面我們來測試下,先在本地啟動一個Tomcat,端口8080,shutdown關口8005。
打開windows cmd命令行
使用telnet命令 telnet 127.0.0.1 8005
輸入SHUTDOWN然后回車,可以看到tomcat控制臺
那么我們都知道,正常我們關閉Tomcat都是調用bin目錄下的shutdown.sh/bat,那么shutdown.bat/sh是做了什么關閉tomcat的呢。
我們利用在文章http://www.cnblogs.com/coldridgeValley/p/5471421.html中學到的方法,可以在shutdown.bat/sh末尾打印出命令可以發現,其實shutdown.bat/sh調用了catalina.bat/sh并且傳遞了一個stop參數,而catalina.bat/sh則是調用了Bootstrap.java類的主方法并且傳遞了參數stop,查看源碼可以看到,如果傳遞的參數是stop,那么就是直接執行Catalina類的stop方法,所以繞來繞去就是無論如何關閉全部是調用Catalina的stop方法,所以大家可以仔細多看幾遍Catalina的stop方法,好了到這里我們Tomcat的啟動過程就全部看完了,我們下面繼續聊點別的。
轉載于:https://www.cnblogs.com/coldridgeValley/p/5631612.html
總結
以上是生活随笔為你收集整理的Tomcat启动过程源码分析六的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android LayoutInfla
- 下一篇: 实现ARM——Linux的自动登录