在tomcat 的server.xml文件中,顶层Server标签可以配置一个监听关闭服务器命令的端口,一旦接收到SHUTDOWN的请求后,则尝试关闭整个tomcat服务器。
具体配置如下:
<Server port="8005" shutdown="SHUTDOWN">
标识监听8005端口,如果接收到的请求为“SHUTDOWN”,那么会触发关闭服务器的操作。
为了实验这个端口好不好使,首先我把tomcat启动了起来,然后在浏览器发送了一个请求: http://localhost:8005/SHUTDOWN
结果tomcat后台日志为:
警告: StandardServer.await: Invalid command 'GET /SHUTDOWN HTTP/1.1' received
并且服务器没有关闭。这次请求收到的内容为 GET /SHUTDOWN HTTP/1.1
而不是SHUTDOWN
,查看源码才发现,这里直接从监听的socket拿的数据,并没有做http请求解析,所以收到了一个http get请求的报文。要想正确发送SHUTDOWN,需要通过socket直接发送数据,使用jdk api的操作如下:
java.net.Socket socket = new java.net.Socket("localhost", 8005);
socket.getOutputStream().write("SHUTDOWN".getBytes());
socket.getOutputStream().flush();
socket.getOutputStream().close();
socket.close();
这样tomcat服务器就能正确关闭了,日志如下:
七月 17, 2017 12:56:59 下午 org.apache.catalina.core.StandardServer await
信息: A valid shutdown command was received via the shutdown port. Stopping the Server instance.
七月 17, 2017 12:56:59 下午 org.apache.coyote.AbstractProtocol pause
信息: Pausing ProtocolHandler ["http-bio-8080"]
七月 17, 2017 12:56:59 下午 org.apache.coyote.AbstractProtocol pause
信息: Pausing ProtocolHandler ["ajp-bio-8009"]
七月 17, 2017 12:56:59 下午 org.apache.catalina.core.StandardService stopInternal
信息: Stopping service Catalina
七月 17, 2017 12:56:59 下午 org.apache.coyote.AbstractProtocol stop
信息: Stopping ProtocolHandler ["http-bio-8080"]
七月 17, 2017 12:56:59 下午 org.apache.coyote.AbstractProtocol stop
信息: Stopping ProtocolHandler ["ajp-bio-8009"]
七月 17, 2017 12:56:59 下午 org.apache.coyote.AbstractProtocol destroy
信息: Destroying ProtocolHandler ["http-bio-8080"]
七月 17, 2017 12:56:59 下午 org.apache.coyote.AbstractProtocol destroy
信息: Destroying ProtocolHandler ["ajp-bio-8009"]
下面看一下源码的实现:
首先Bootstrap类调用了Catalina的start()方法启动服务器,在start()方法最后,有如下代码:
/**
* Catalina类
* Start a new server instance.
*/
public void start() {
...
if (await) {
await();
stop();
}
}
变量await在启动时默认为true,await()方法如下:
/**
* Catalina类
* Await and shutdown.
*/
public void await() {
getServer().await();
}
Catalina类中的Server默认实现类为StandardServer,其await()方法如下:
//StandardServer类
//简化版源码
public void await() {
//如果端口为-2或-1的处理方式
//指定了正常端口
//首先监听此端口
awaitSocket = new ServerSocket(port, 1,
InetAddress.getByName(address));
// Loop waiting for a connection and a valid command
//循环
while (!stopAwait) {
...
//阻塞在这里 等待数据
socket = serverSocket.accept();
//接收到数据后往下执行 读取数据
// Read a set of characters from the 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--;
}
// Match against our command string
boolean match = command.toString().equals(shutdown);
//如果接收到的请求为"SHUTDOWN"或其他正确的自定义的字符串
if (match) {
log.info(sm.getString("standardServer.shutdownViaPort"));
//跳出while循环 await()方法结束,线程执行返回到Catalina类的start()方法,继续执行Catalina的stop()方法
break;
} else
log.warn("StandardServer.await: Invalid command '"
+ command.toString() + "' received");
下面看一下Catalina的stop()方法,此方法是真正触发tomcat服务器destroy生命周期的方法:
public void stop() {
...
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 {
s.stop();
s.destroy();
}
}
s即StandardServer,首先判断s的状态,该状态是一个volatile变量,如果状态为正在销毁,则不进行任何操作;否则,调用s的stop()和destroy()方法。
stop()和destroy()方法为抽象类LifecycleBase的方法,tomcat中凡是拥有生命周期的组件均继承自此抽象类,并且这两个方法使用了 模板方法模式 ,方法内部实现了一部分更改组件状态的代码,之后调用了抽象方法stopInternal()和stopDestroy(),这俩方法的作用是留给子类去实现。
在StandardServer的stopInternal()方法中,会依次调用子组件Service的stop()方法:
@Override
protected void stopInternal() throws LifecycleException {
setState(LifecycleState.STOPPING);
fireLifecycleEvent(CONFIGURE_STOP_EVENT, null);
// Stop our defined Services
for (int i = 0; i < services.length; i++) {
services[i].stop();
}
globalNamingResources.stop();
stopAwait();
}
StandardService的stopInternal()方法如下:
protected void stopInternal() throws LifecycleException {
// Pause connectors first 首先停止connector再接收新的请求
synchronized (connectorsLock) {
for (Connector connector: connectors) {
connector.pause();
}
}
//设置状态
setState(LifecycleState.STOPPING);
// Stop our defined Container second 关闭容器,顶层为StandardEngine
if (container != null) {
synchronized (container) {
container.stop();
}
}
// Now stop the connectors 停止connector
synchronized (connectorsLock) {
for (Connector connector: connectors) {
connector.stop();
}
}
synchronized (executors) { 关闭线程池
for (Executor executor: executors) {
executor.stop();
}
}
}
之后,每层组件都是这样一步一步的向下调用,链式触发整个tomcat组件destory的生命周期,完成资源的释放和销毁,最终关闭服务器。
stop和destroy相似,只是工作不同,有兴趣的同志可以继续深入研究源码。