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

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

你可能感兴趣的:(tomcat)