前言

上一篇文章中我们讨论了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()

因为CatalinaShutdownHookCatalina类的内部类,所以这句代码就是指向了外部类对象并且调用了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方法,我们继续查看StandardServicestop方法。

 @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方法,我们只浏览到这部分,下面的读者可以参照之前查看startinit方法的形式自行往下查看源码。到这里StandardServerstop方法我们就看完了,但是在钩子代码里还调用了StandardServerdestroy方法,我们继续查看下StandardServerdestroy方法。

@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很类似,也是调用了StandardServicedestroy方法,我们就不继续查看了,留给读者自行查看,那么我们钩子方法就看完了。可以看出如果tomcat出现了异常关闭,那么最终是调用的Catalinastop方法,而Catalinastop方法又调用StandardServerstopdestroy方法,我们继续往下看start方法的最后一部分。

//2222222
if (await) {await();stop();
}

await属性在Bootstrap类的main方法中被设置为true,查看wait方法:

/*** Await and shutdown.*/
public void await() {getServer().await();}

调用StandardServerawait方法,由于代码很多,省略了部分,只展示了部分重要代码

 /*** 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.xmlServer标签的port,新建了socket以后,调用其accept方法等待,也就是说如果在对应ip,port上有请求进来就会被这里接收到。我们继续看代码2,在第二步拿到accept到的线程的输入流,从输入流中读取拼装字符串,第三步把拼装好的字符串和变量shutdown比对,如果相同就跳出循环。我们查看shutdown指向的是一个SHUTDOWN字符串。看到这里有点不明所以,但是await方法就这样结束了,方法的作用我们暂时不说先继续往下看,查看Catalinastart方法的最后一点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方法,所以绕来绕去就是无论如何关闭全部是调用Catalinastop方法,所以大家可以仔细多看几遍Catalinastop方法,好了到这里我们Tomcat的启动过程就全部看完了,我们下面继续聊点别的。

转载于:https://www.cnblogs.com/coldridgeValley/p/5631612.html

Tomcat启动过程源码分析六相关推荐

  1. Tomcat启动过程源码分析四

    前言 上一篇文章中我们讨论了Bootstrap类中main方法中涉及到的init方法,今天这篇文章我们来查看下load方法. daemon.setAwait(true); daemon.load(ar ...

  2. SpringBoot2 | SpringBoot启动流程源码分析(一)

    首页 博客 专栏·视频 下载 论坛 问答 代码 直播 能力认证 高校 会员中心 收藏 动态 消息 创作中心 SpringBoot2 | SpringBoot启动流程源码分析(一) 置顶 张书康 201 ...

  3. Android系统默认Home应用程序(Launcher)的启动过程源码分析

    在前面一篇文章中,我们分析了Android系统在启动时安装应用程序的过程,这些应用程序安装好之后,还须要有一个Home应用程序来负责把它们在桌面上展示出来,在Android系统中,这个默认的Home应 ...

  4. Activity启动流程源码分析-浅析生命周期函数

    源码分析 接着上一篇 Activity启动流程源码分析-setContentView源码阅读 的讲解,本节介绍一下Activity的生命周期函数何时被调用 要看Activity的生命周期函数何时被调用 ...

  5. Activity启动流程源码分析(基于Android N)

    Activity启动流程源码分析 一个Activity启动分为两种启动方式,一种是从Launcher界面上的图标点击启动,另一种是从一个Activity中设置按钮点击启动另外一个Activity.这里 ...

  6. Tomcat启动过程源码解读

    根据Tomcat源码来看一下Tomcat启动过程都做了什么 部分代码为主要流程代码,删去了try-catch以及一些校验逻辑,方便理解主流程 先来一张启动过程时序图,了解一下启动顺序 Tomcat启动 ...

  7. Activity启动过程源码分析

    老罗的Android系统源码分析讲的很不错,网上有很不同层面多源码分析.了解细节,还是自己看源码最直接.个人并没有透彻的研究过Android系统,这一系列的博客就当是读Android源码笔记了.有不对 ...

  8. DataNode启动流程源码分析

    我们都知道在Hadoop hdfs文件系统中,Datanode是负责hdfs文件对应的数据块存储管理的组件,其会在启动时向NameNode汇报其上拥有的数据块,以及周期性心跳并接收来自NameNode ...

  9. SpringBoot配置外部Tomcat项目启动流程源码分析(下)

    前言 SpringBoot应用默认以Jar包方式并且使用内置Servlet容器(默认Tomcat),该种方式虽然简单但是默认不支持JSP并且优化容器比较复杂.故而我们可以使用习惯的外置Tomcat方式 ...

最新文章

  1. 从源代码的角度分析--在BaseAdapter调用notifyDataSetChanged()之后发生了什么
  2. Django-form表单
  3. 深度学习之循环神经网络(1)序列表示方法
  4. kaggle(01)-泰坦尼克号问题
  5. linux下的C语言开发(进程创建)
  6. C# 连接远程MySql出错,显示表不存在 [ C# | MySql | Table 'TABLENAME' doesn't exist ]
  7. 【动态规划】P1220:区间dp:关路灯
  8. 31.go 函数式编程
  9. 关于a标签下载文件变打开的解决方案
  10. ASP.Net中控件的EnableViewState属性
  11. 前端使用阿里云图标库
  12. vue 项目 build 之后dist文件下的index.html不显示内容,并且报 Failed to load resource: net::ERR_FILE_NOT_FOUND 错误
  13. 角度值计算机符号,数学角度符号_请问各种数学符号的读音比如αβγδελζηθξσφψω等等的读音_淘题吧...
  14. 如何计算离职率(1)?
  15. android studio中崩溃无法查看log?
  16. python全案例学习_Python全案例学习与实践
  17. Linux 基础命令 -- usermod
  18. 利用词向量计算上下位关系
  19. C51中intrins_h头文件解释分析
  20. dockers-系统学习-docker compose服务编排

热门文章

  1. mysql 修改max_connections
  2. 【android开发】Android防止内存溢出浅析
  3. 解决VS2008 开发Windows Mobile 项目生成速度慢的问题
  4. (转)Symbian启动J2ME程序
  5. 利用OpenCV的imread将RGB图像转化为灰度图像
  6. php帝国程序跨表调用数组,帝国CMS 跨表调用相关信息标签
  7. linux下diff、patch制作补丁
  8. ruby gem install rails 错误解决方法
  9. acwing算法题--整数分解
  10. vim-go开发环境Tagbar插件和NERTree插件安装