189 8069 5689

tomcatserver组件监听shutdown命令关闭服务器之源码分析

在tomcat 的server.xml文件中,顶层Server标签可以配置一个监听关闭服务器命令的端口,一旦接收到SHUTDOWN的请求后,则尝试关闭整个tomcat服务器。

渭滨网站制作公司哪家好,找创新互联!从网页设计、网站建设、微信开发、APP开发、响应式网站建设等网站项目制作,到程序开发,运营维护。创新互联2013年开创至今到现在10年的时间,我们拥有了丰富的建站经验和运维经验,来保证我们的工作的顺利进行。专注于网站建设就选创新互联。

具体配置如下:

标识监听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相似,只是工作不同,有兴趣的同志可以继续深入研究源码。


网站名称:tomcatserver组件监听shutdown命令关闭服务器之源码分析
文章来源:http://cdxtjz.cn/article/chojjp.html