Tomcat组件梳理--Server

1.Server组件的定义和功能概述

定义:

Server组件用于描述一个启动的Tomcat实例,一个Tocmat被启动,在操作系统中占用一个进程号,提供web服务的功能,那个这个整个服务用Server来表示。

功能

Server作为描述一个Tomcat服务的组件,需要有对应的启动,停止方法,请求接收和处理方法等。所有的方法都是Server组件内部的一个子组件来实现。

总结就是,Server代表Tomcat服务实例,Tomcat所有提供的功能,都由Server组件中的子组件来实现。

2.Server组件的具体形式

明白了Server是干嘛的,那么这个组件到底长什么样子呢?我们可以从代码里来看这个组件到底长什么样子,由什么组成。Server从抽象层面来看,代表了一个Tomcat实例,但落在代码中还是一个Java的Class类。因此,我们先来看看Server类到底有哪些属性,就知道这个组件长了什么样子。

把Server类的代码中的属性抽取出来看如下:

public final class StandardServer{
//1.java命令和地址服务相关
/**
* JNDI的context
*/
private javax.naming.Context globalNamingContext = null;
/**
* JNDI的resource
*/
private NamingResourcesImpl globalNamingResources = null;
/**
* 作用与web application的JDNI监听器
*/
private final NamingContextListener namingContextListener; //2.shutdown服务相关
/**
* 用于等待shutdown命令的端口号
*/
private int port = 8005;
/**
* 用于等待shutdown命令的地址
*/
private String address = "localhost";
/**
* shutdown服务在等待的命令
*/
private String shutdown = "SHUTDOWN";
/**
* 执行await()的线程
*/
private volatile Thread awaitThread = null; /**
* 用于等待shutdown的socket对象
*/
private volatile ServerSocket awaitSocket = null;
/**
* stopAwait的标记位
*/
private volatile boolean stopAwait = false; //3.子组件Service相关
/**
* Server下的service集合
*/
private Service services[] = new Service[0];
/**
* Service用到的锁
*/
private final Object servicesLock = new Object(); //4.父类加载器
//父类加载器
private ClassLoader parentClassLoader = null; //5.catalina相关的
//catalinaHome的地址
private File catalinaHome = null; //catalinaBase的地址
private File catalinaBase = null;
}

如代码里展现出来的,长的样子主要从以下5个点来描述:

  • 1.JNDI相关
  • 2.停止公告shutdown相关
  • 3.子组件Service相关
  • 4.类加载器相关
  • 5.Catalina相关(Catalina作为Tomcat的启动组件,从这个类中启动Server)

以上,Server的重要属性和特点都在这里了,当然还有很多功能都是在Service组件中进行定义的。这些等我们来做Service组件分析时,再详细说明。

3.Server组件的具体功能

在上面中,我们知道了Server的作用和重要属性,现在我们就来看看Server的重要功能。

3.1.如何启动Server

启动Server需要调用两个方法,在真正的start()方法之前还需要执行init()方法,init方法是一个pre-start()方法。

看一下在Server中,这两个方法的具体表现。

首先是init的方法。init方法里需要做5个逻辑处理,分别是:

  • 1.调用父类的init()方法
  • 2.注册全局的String cache,作用是什么现在还不太清楚
  • 3.注册MBean,使通过JMX能够监控
  • 4.调用JNDI的init方法
  • 5.对common和shared目录下的jar包进行校验,如果给出的jar文件包含MANIFEST,将被添加到container的manifest资源中.
  • 6.执行Service的init方法
 protected void initInternal() throws LifecycleException {

        //1.先调用父类的init方法
super.initInternal(); //2.注册全局的String cache,作用是什么现在还不清楚
onameStringCache = register(new StringCache(), "type=StringCache"); //3.注册JMX的MBean
MBeanFactory factory = new MBeanFactory();
factory.setContainer(this);
onameMBeanFactory = register(factory, "type=MBeanFactory"); //4.调用JNDI的init方法
globalNamingResources.init(); // Populate the extension validator with JARs from common and shared class loaders
//5.验证
if (getCatalina() != null) {
ClassLoader cl = getCatalina().getParentClassLoader();
// Walk the class loader hierarchy. Stop at the system class loader.
// This will add the shared (if present) and common class loaders
while (cl != null && cl != ClassLoader.getSystemClassLoader()) {
if (cl instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url : urls) {
if (url.getProtocol().equals("file")) {
try {
File f = new File (url.toURI());
if (f.isFile() &&
f.getName().endsWith(".jar")) {
ExtensionValidator.addSystemResource(f);
}
} catch (URISyntaxException e) {
// Ignore
} catch (IOException e) {
// Ignore
}
}
}
}
cl = cl.getParent();
}
}
//6.执行Service的init方法
for (int i = 0; i < services.length; i++) {
services[i].init();
}
}

然后是start()方法,该方法主要处理以下3个逻辑:

  • 1.更新生命周期的状态,并发送生命周期时间,用来触发监听器
  • 2.启动JNDI
  • 3.启动Server中的所有Service组件。
    @Override
protected void startInternal() throws LifecycleException { //1.发送生命周期时间
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
//更改生命周期的状态为starting
setState(LifecycleState.STARTING); //2.启动JDNI
globalNamingResources.start(); //3.调用Service的start()方法,启动Service
synchronized (servicesLock) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}

3.2.如何关闭Server

对Server的关闭,先提两个问题:1.应该在什么时候进行关闭,2.要如何关闭。

第一个问题,Tomcat的解决方法时,Tomcat的main线程在启动完所有的组件后,自己开一个socket服务端,在指定的端口上进行监听,一直到有shutdown命令发送过来,就退出socket的等待,开始执行关闭方法。

第二个问题,Server的关闭依旧放在Tomcat的生命周期中的stop方法和destroy方法中进行处理。

虽然上面两个问题有解决方案了,但是应该还是有不清楚的地方,先看一下这两个问题在Catalina类中的调用顺序吧:

//org.apache.catalina.startup.Catalina#start
if (await) {
await();
stop();
}

如上,即判断一下是不是要await,如果需要,就调用Server的awati()方法开始循环等待socket连接,直到有一个匹配的命令行进来,然后结束await()方法的执行,开始执行stop()方法,开始处理停止相关的动作。

先看第一个问题即await()在Server中的具体实现,该方法主要处理逻辑分4步:

  • 1.处理port等于-1或-2的情况。
  • 2.在指定的端口上开启一个socket的服务端
  • 3.在socket上进行等待连接,并对连接进行接收处理,与shutdown命令进行匹配,如果相等,就跳出循环。
  • 4.清理打开的资源。
public void await() {
//1.处理port等于-1或-2的情况。
// Negative values - don't wait on port - tomcat is embedded or we just don't like ports
if( port == -2 ) {
// undocumented yet - for embedding apps that are around, alive.
return;
}
if( port==-1 ) {
try {
awaitThread = Thread.currentThread();
while(!stopAwait) {
try {
Thread.sleep( 10000 );
} catch( InterruptedException ex ) {
// continue and check the flag
}
}
} finally {
awaitThread = null;
}
return;
} //2.设置一个socket的服务端在指定端口上进行等待
try {
awaitSocket = new ServerSocket(port, 1,
InetAddress.getByName(address));
} catch (IOException e) {
return;
} try {
//当前线程设置为等待线程
awaitThread = Thread.currentThread(); //3.轮询等待,直到shutdown命令行进来,如果跟设置的匹配就结束循环
while (!stopAwait) {
ServerSocket serverSocket = awaitSocket;
if (serverSocket == null) {
break;
} // Wait for the next connection
Socket socket = null;
StringBuilder command = new StringBuilder();
try {
InputStream stream;
long acceptStartTime = System.currentTimeMillis();
//3.1.等待连接,并拿到输入流inputSteam
socket = serverSocket.accept();
socket.setSoTimeout(10 * 1000);
stream = socket.getInputStream(); //3.2.有连接进来时,从socket中读取字节,转成字符串
int expected = 1024; // Cut off to avoid DoS attack
while (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;
}
// Control character or EOF (-1) terminates loop
if (ch < 32 || ch == 127) {
break;
}
command.append((char) ch);
expected--;
}
} finally {
// Close the socket now that we are done with it
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
// Ignore
}
} // Match against our command string
//3.3.把传进来的字符串跟内置的进行匹配,如果如果一样就结束等待
boolean match = command.toString().equals(shutdown);
if (match) {
log.info(sm.getString("standardServer.shutdownViaPort"));
break;
} }
//4.清理资源
} finally {
ServerSocket serverSocket = awaitSocket;
awaitThread = null;
awaitSocket = null; // Close the server socket and return
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
// Ignore
}
}
}
}

await()方法执行完之后,就自动开始调用stop()方法了,我们来开始看stop()方法里面的内容,注意,此时是在Catalina类中的stop方法

// org.apache.catalina.startup.Catalina#stop
// Shut down the server
try {
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 {
//调用Server的stop方法
s.stop();
//调用Server的destroy方法
s.destroy();
}
} catch (LifecycleException e) {
log.error("Catalina.stop", e);
}

这里应该比较容易看懂,Catalina的stop方法会调用Server的stop()方法和destroy()方法。

此时再依次看Server的stop()方法,该方法主要处理4个逻辑:

  • 1.设置生命周期的转态,并推送生命周期的事件
  • 2.调用所有的service的stop方法
  • 3.停止JNDI
  • 4.清理扥带线程和socket。
protected void stopInternal() throws LifecycleException {

  //1.设置生命周期的状态,
setState(LifecycleState.STOPPING);
fireLifecycleEvent(CONFIGURE_STOP_EVENT, null); // 2.调用所有的Service的stop方法
for (int i = 0; i < services.length; i++) {
services[i].stop();
} //3.停止JDNI
globalNamingResources.stop(); //4.清理等待线程和socket
stopAwait();
}

然后是destroy方法,该方法中有4个处理逻辑,

  • 1.调用所有的service的destroy()方法
  • 2.调用JNDI的destroy()
  • 3.解绑MBean,和String Cache,对应init方法中的注册。
  • 4.调用父类的方法
protected void destroyInternal() throws LifecycleException {
// 1.调用所有的service的destroy()方法
for (int i = 0; i < services.length; i++) {
services[i].destroy();
} //2.调用JNDI的destroy()
globalNamingResources.destroy(); //3.解绑MBean,和String Cache,对应init方法中的注册。
unregister(onameMBeanFactory); unregister(onameStringCache); //4.调用父类的方法
super.destroyInternal();
}

经过上面这些步骤,整个Server的服务就被停止下来了。还有很多子组件,也会被停止,不过都是调用相同的生命周期,会在每个子组件中详细说。

4.Server组件跟其他组件的联系

现在,我们明白了Server组件自己的特有属性和自己的功能,但是他跟其他组件的联系是怎么样的?其实在上面两个动作中,已经能看到,主要有:Service组件,JDNI组件,JMX组件,类加载器组件。

针对Service组件来说,一个Server组件包含多个Service,其实也可以看到,Server的主要功能,就是管理好所有的Service,逻辑都在Service中。

JNDI组件现在不详细探索,先探索Tomcat主要组件,然后再解决这个问题。JMX组件同样。

针对类加载器,Server只提供了一个设置和获取父类加载器的方法和属性,并没有提供自己的类加载器属性和方法,这里其实是有点不太懂的。

5.总结

以上,通过分析,我们知道Server主要是Tomcat服务的承担体,自己主要承担的是启动它下面的所有组件以及停止,以及对Service组件的管理。

Tomcat组件梳理--Server的更多相关文章

  1. Tomcat组件梳理—Service组件

    Tomcat组件梳理-Service组件 1.组件定义 Tomcat中只有一个Server,一个Server可以用多个Service,一个Service可以有多个Connector和一个Contain ...

  2. Tomcat组件梳理—Digester的使用

    Tomcat组件梳理-Digester的使用 再吐槽一下,本来以为可以不用再开一个篇章来梳理Digester了,但是发现在研究Service的创建时,还是对Digester的很多接口或者机制不熟悉,简 ...

  3. Tomcat组件梳理--Catalina

    Tomcat组件梳理--Catalina 1.定义和功能 Catalina是Tomcat的核心组件,是Servlet容器,Catalina包含了所有的容器组件,其他模块均为Catalina提供支撑.通 ...

  4. 1.Tomcat组件梳理—Bootstrap启动器

    Tomcat组件梳理-Bootstrap启动器 一开始是直接从Server开始做梳理的,但是发现有很多东西是从Catalina传输过来的,Catalina又是从Bootstrap启动的,所以还是回过头 ...

  5. web服务器专题:tomcat(二)模块组件与server.xml 配置文件

    web服务器专题:tomcat(二)模块组件与server.xml 配置文件 回顾: Web服务器专题:tomcat(一) 基础模块 一个Server.xml的实例 <?xml version= ...

  6. 【转】Tomcat组件生命周期管理

    Tomcat组件生命周期管理 Tomcat中Server,Service,Connector,Engine,Host,Context,它们都实现了org.apache.catalina.Lifecyc ...

  7. Tomcat 组件介绍

    用了好长时间tomcat,但是其实自己只是反复听了这个名字,对Tomcat并不了解 1.Tomcat组件 Catalina Coyote Jasper Cluster 2.组件介绍 Tomcat Co ...

  8. Tomcat系列(4)——Tomcat 组件及架构详细部分

    核心部分   1. 定义 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta ...

  9. Tomcat系列(3)——Tomcat 组件及架构核心部分 4类主要组件(顶层,连接器,容器,嵌套)

    1.架构图 2. 定义 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta ...

随机推荐

  1. [技术博客]Pyqt中View类别容器和Widget类别容器的区别

    Pyqt中View类别容器和Widget类别容器的区别 简介 在beta迭代中,我们选择用pyqt5来重写alpha迭代中使用tkinter库编写的界面. ​ 按钮之类的与tkiner使用无异,在显示 ...

  2. 《微信小程序商城开发实战》唐磊,全网真实评价截图,不吹不黑,全部来自网友的真实评价

    偶尔看了下网友的销量和评价,感觉还不错,因为市面上大多关于小程序的书籍可能写的不够全面,要么只是点到为止的大致罗列,要么就是只简单介绍一下小程序的 界面设计这块.这样很难给学习小程序开发的人一个完成的 ...

  3. 4款五星级的3D模型资源包

    HI,晚上好各位,今晚我们将为大家介绍4款五星级的3D模型资源包. ANIMALS FULL PACK ANIMALS FULL PACK包含了由PROTOFACTOR制作的24款高质量的动物模型,包 ...

  4. 虚拟环境上的jupyterhub开机启动设置

    为了让jupyterhub 开机启动,或者以服务的方式启动,折腾了好久.环境 ubuntu 16.04anaconda >= 4.5python35 jupyterhub 0.9.4node 6 ...

  5. 超实用!手把手教你如何用MSF进行后渗透测试!

    在对目标进行渗透测试的时候,通常情况下,我们首先获得的是一台web服务器的webshell或者反弹shell,如果权限比较低,则需要进行权限提升:后续需要对系统进行全面的分析,搞清楚系统的用途:如果目 ...

  6. 信息熵 Information Entropy

    信息熵用于描述信源的不确定度, 即用数学语言描述概率与信息冗余度的关系. C. E. Shannon 在 1948 年发表的论文A Mathematical Theory of Communicati ...

  7. cesium地形瓦片(HeightMap)格式

    目录 1.瓦片切分规则 2..terrain瓦片格式分析 参考资料: heightmap 1.0 Tile Map Service Specification 国内主要地图瓦片坐标系定义及计算原理 H ...

  8. python开源项目聚合推荐【1】

    ******************************************************* 01项目名:unimatrix 功能介绍:Python模拟“黑客帝国”影片中的终端动画脚 ...

  9. odoo开发笔记 -- 借助模块queue_job实现异步方法调用

    场景描述: 对比了几个定时调度的框架,发现各有优缺点: celery 很强,异步定时调度,异步周期调度,也有延时调度的功能,但是延时调度的案例比较少,遂暂时不使用. queue_job,一个odoo第 ...

  10. 如何分析redis中的慢查询

    慢查询的两个参数配置 慢查询只记录命令执行时间,并不包括命令排队和网络传输时间.因此客户端执行命令的时间会大于命令实际执行时间.因为命令执行排队机制,慢查询会导致其他命令级联阻塞,因此当客户端出现请求 ...